From 63ed88abbb38c00f4da86d93dc4b80cf53076072 Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Wed, 10 Dec 2025 13:11:24 +0000 Subject: [PATCH 01/12] Changes --- src/contracts/Random.h | 602 +++++++++++++++++++++++++++++++++++++- test/contract_random.cpp | 511 ++++++++++++++++++++++++++++++++ test/test.vcxproj | 1 + test/test.vcxproj.filters | 1 + 4 files changed, 1105 insertions(+), 10 deletions(-) create mode 100644 test/contract_random.cpp diff --git a/src/contracts/Random.h b/src/contracts/Random.h index e62340d70..c6d1569c5 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -1,40 +1,622 @@ using namespace QPI; +// Max miners to consider for distribution +#define MAX_RECENT_MINERS 369 +#define ENTROPY_HISTORY_LEN 3 // For 2-tick-back entropy pool + struct RANDOM2 { }; struct RANDOM : public ContractBase { +private: + // Entropy pool history (circular buffer for look-back) + m256i entropyHistory[ENTROPY_HISTORY_LEN]; + uint64 entropyPoolVersionHistory[ENTROPY_HISTORY_LEN]; + uint32 entropyHistoryHead; // points to most recent + + // Global entropy pool - combines all revealed entropy + m256i currentEntropyPool; + uint64 entropyPoolVersion; + + // Tracking statistics + uint64 totalCommits; + uint64 totalReveals; + uint64 totalSecurityDepositsLocked; + + // Contract configuration + uint64 minimumSecurityDeposit; + uint32 revealTimeoutTicks; // e.g. 9 ticks + + // Revenue distribution system + uint64 totalRevenue; + uint64 pendingShareholderDistribution; + uint64 lostDepositsRevenue; + + // Earnings pools + uint64 minerEarningsPool; + uint64 shareholderEarningsPool; + + // Pricing config + uint64 pricePerByte; // e.g. 10 QU (default) + uint64 priceDepositDivisor; // e.g. 1000 (matches contract formula) + + // Epoch tracking for miner rewards + struct RecentMiner { + id minerId; + uint64 deposit; + uint64 lastEntropyVersion; + uint32 lastRevealTick; + } recentMiners[MAX_RECENT_MINERS]; + uint32 recentMinerCount; + + // Valid deposit amounts (powers of 10) + uint64 validDepositAmounts[16]; + + // Commitment tracking + struct EntropyCommitment { + id digest; + id invocatorId; + uint64 amount; + uint32 commitTick; + uint32 revealDeadlineTick; + bool hasRevealed; + } commitments[1024]; + uint32 commitmentCount; + + // Helper functions (static inline) + static inline void updateEntropyPoolData(RANDOM& stateRef, const bit_4096& newEntropy) + { + // XOR new entropy into the global pool + // Access BitArray internal data through a uint64 pointer cast + const uint64* entropyData = reinterpret_cast(&newEntropy); + for (uint32 i = 0; i < 4; i++) + stateRef.currentEntropyPool.m256i_u64[i] ^= entropyData[i]; + + // Update entropy history (circular buffer) + stateRef.entropyHistoryHead = (stateRef.entropyHistoryHead + 1U) % ENTROPY_HISTORY_LEN; + stateRef.entropyHistory[stateRef.entropyHistoryHead] = stateRef.currentEntropyPool; + stateRef.entropyPoolVersion++; + stateRef.entropyPoolVersionHistory[stateRef.entropyHistoryHead] = stateRef.entropyPoolVersion; + } + + static inline void generateRandomBytesData(const RANDOM& stateRef, uint8* output, uint32 numBytes, uint32 historyIdx, uint32 currentTick) + { + const m256i selectedPool = stateRef.entropyHistory[(stateRef.entropyHistoryHead + ENTROPY_HISTORY_LEN - historyIdx) % ENTROPY_HISTORY_LEN]; + m256i tickEntropy; + tickEntropy.m256i_u64[0] = static_cast(currentTick); + tickEntropy.m256i_u64[1] = 0; + tickEntropy.m256i_u64[2] = 0; + tickEntropy.m256i_u64[3] = 0; + m256i combinedEntropy; + for (uint32 i = 0; i < 4; i++) + combinedEntropy.m256i_u64[i] = selectedPool.m256i_u64[i] ^ tickEntropy.m256i_u64[i]; + + // Copy bytes from the combined entropy to output + for (uint32 i = 0; i < ((numBytes > 32U) ? 32U : numBytes); i++) + output[i] = combinedEntropy.m256i_u8[i]; + } + + static inline bool isValidDepositAmountCheck(const RANDOM& stateRef, uint64 amount) + { + for (uint32 i = 0; i < 16U; i++) + if (amount == stateRef.validDepositAmounts[i]) return true; + return false; + } + + static inline bool isEqualIdCheck(const id& a, const id& b) + { + for (uint32 i = 0; i < 32U; i++) + if (a.m256i_u8[i] != b.m256i_u8[i]) return false; + return true; + } + + static inline bool isZeroIdCheck(const id& value) + { + for (uint32 i = 0; i < 32U; i++) + if (value.m256i_u8[i] != 0) return false; + return true; + } + + static inline bool isZeroBitsCheck(const bit_4096& value) + { + // Access BitArray internal data through a uint64 pointer cast + const uint64* data = reinterpret_cast(&value); + for (uint32 i = 0; i < 64U; i++) + if (data[i] != 0) return false; + return true; + } + + // Helper functions (static inline) + static inline bool k12CommitmentMatches(const QPI::QpiContextFunctionCall& qpi, const QPI::bit_4096& revealedBits, const QPI::id& committedDigest) + { + // QPI K12 returns id (m256i) + QPI::id computedDigest = qpi.K12(revealedBits); + for (QPI::uint32 i = 0; i < 32U; i++) + if (computedDigest.m256i_u8[i] != committedDigest.m256i_u8[i]) return false; + return true; + } + public: + // -------------------------------------------------- + // Entropy mining (commit-reveal) struct RevealAndCommit_input { - bit_4096 revealedBits; - id committedDigest; + bit_4096 revealedBits; // Previous entropy to reveal (or zeros for first commit) + id committedDigest; // Hash of new entropy to commit (or zeros if stopping) }; + struct RevealAndCommit_output { + uint8 randomBytes[32]; + uint64 entropyVersion; + bool revealSuccessful; + bool commitSuccessful; + uint64 depositReturned; }; -private: - uint64 _earnedAmount; - uint64 _distributedAmount; - uint64 _burnedAmount; + // -------------------------------------------------- + // READ-ONLY FUNCTIONS + + struct GetContractInfo_input {}; + struct GetContractInfo_output + { + uint64 totalCommits; + uint64 totalReveals; + uint64 totalSecurityDepositsLocked; + uint64 minimumSecurityDeposit; + uint32 revealTimeoutTicks; + uint32 activeCommitments; + uint64 validDepositAmounts[16]; + uint32 currentTick; + uint64 entropyPoolVersion; + // Revenue + pools + uint64 totalRevenue; + uint64 pendingShareholderDistribution; + uint64 lostDepositsRevenue; + uint64 minerEarningsPool; + uint64 shareholderEarningsPool; + uint32 recentMinerCount; + }; + + struct GetUserCommitments_input { id userId; }; + struct GetUserCommitments_output + { + struct UserCommitment { + id digest; + uint64 amount; + uint32 commitTick; + uint32 revealDeadlineTick; + bool hasRevealed; + } commitments[32]; + uint32 commitmentCount; + }; - uint32 _bitFee; // Amount of qus + // -------------------------------------------------- + // SELL ENTROPY (random bytes) + struct BuyEntropy_input + { + uint32 numberOfBytes; // 1-32 + uint64 minMinerDeposit; // required deposit of recent miner + }; + struct BuyEntropy_output + { + bool success; + uint8 randomBytes[32]; + uint64 entropyVersion; // version of pool 2 ticks ago! + uint64 usedMinerDeposit; + uint64 usedPoolVersion; + }; + // -------------------------------------------------- + // CLAIMING + struct ClaimMinerEarnings_input {}; + struct ClaimMinerEarnings_output + { + uint64 payout; + }; + + // -------------------------------------------------- + // PRICE QUERY + struct QueryPrice_input { + uint32 numberOfBytes; + uint64 minMinerDeposit; + }; + struct QueryPrice_output { + uint64 price; + }; + + // -------------------------------------------------- + // Mining: RevealAndCommit PUBLIC_PROCEDURE(RevealAndCommit) { - qpi.transfer(qpi.invocator(), qpi.invocationReward()); + // Process timeouts first + uint32 currentTick = qpi.tick(); + for (uint32 i = 0; i < state.commitmentCount; ) { + if (!state.commitments[i].hasRevealed && + currentTick > state.commitments[i].revealDeadlineTick) + { + uint64 lostDeposit = state.commitments[i].amount; + state.lostDepositsRevenue += lostDeposit; + state.totalRevenue += lostDeposit; + state.pendingShareholderDistribution += lostDeposit; + state.totalSecurityDepositsLocked -= lostDeposit; + if (i != state.commitmentCount - 1) + state.commitments[i] = state.commitments[state.commitmentCount - 1]; + state.commitmentCount--; + } + else { + i++; + } + } + + // Empty tick handling: + if (qpi.numberOfTickTransactions() == -1) { + for (uint32 i = 0; i < state.commitmentCount; ) { + if (!state.commitments[i].hasRevealed && + state.commitments[i].revealDeadlineTick == qpi.tick()) + { + qpi.transfer(state.commitments[i].invocatorId, state.commitments[i].amount); + state.totalSecurityDepositsLocked -= state.commitments[i].amount; + // Remove this slot by moving last in + if (i != state.commitmentCount - 1) + state.commitments[i] = state.commitments[state.commitmentCount - 1]; + state.commitmentCount--; + // Do not increment i, so the moved entry is checked next + } + else { + i++; + } + } + copyMemory(output, RevealAndCommit_output{}); + return; + } + + const RevealAndCommit_input& inputData = input; + id invocatorId = qpi.invocator(); + uint64 invocatorAmount = qpi.invocationReward(); + copyMemory(output, RevealAndCommit_output{}); + + bool hasRevealData = !isZeroBitsCheck(inputData.revealedBits); + bool hasNewCommit = !isZeroIdCheck(inputData.committedDigest); + bool isStoppingMining = (invocatorAmount == 0); + + // Step 1: Process reveal if provided + if (hasRevealData) + { + for (uint32 i = 0; i < state.commitmentCount; ) { + if (!state.commitments[i].hasRevealed && + isEqualIdCheck(state.commitments[i].invocatorId, invocatorId)) + { + // Use QPI K12 for cryptographic binding + bool hashMatches = k12CommitmentMatches(qpi, inputData.revealedBits, state.commitments[i].digest); + if (hashMatches) + { + if (currentTick > state.commitments[i].revealDeadlineTick) + { + uint64 lostDeposit = state.commitments[i].amount; + state.lostDepositsRevenue += lostDeposit; + state.totalRevenue += lostDeposit; + state.pendingShareholderDistribution += lostDeposit; + output.revealSuccessful = false; + } + else + { + updateEntropyPoolData(state, inputData.revealedBits); + + qpi.transfer(invocatorId, state.commitments[i].amount); + + output.revealSuccessful = true; + output.depositReturned = state.commitments[i].amount; + + state.totalReveals++; + state.totalSecurityDepositsLocked -= state.commitments[i].amount; + + // Update RecentMiners (with per-miner freshness) + sint32 existingIndex = -1; + for (uint32 rm = 0; rm < state.recentMinerCount; ++rm) { + if (isEqualIdCheck(state.recentMiners[rm].minerId, invocatorId)) { + existingIndex = rm; + break; + } + } + if (existingIndex >= 0) { + if (state.recentMiners[existingIndex].deposit < state.commitments[i].amount) { + state.recentMiners[existingIndex].deposit = state.commitments[i].amount; + state.recentMiners[existingIndex].lastEntropyVersion = state.entropyPoolVersion; + } + state.recentMiners[existingIndex].lastRevealTick = currentTick; + } + else { + if (state.recentMinerCount < MAX_RECENT_MINERS) { + state.recentMiners[state.recentMinerCount].minerId = invocatorId; + state.recentMiners[state.recentMinerCount].deposit = state.commitments[i].amount; + state.recentMiners[state.recentMinerCount].lastEntropyVersion = state.entropyPoolVersion; + state.recentMiners[state.recentMinerCount].lastRevealTick = currentTick; + state.recentMinerCount++; + } + else { + // Overflow: evict + uint32 lowestIx = 0; + for (uint32 rm = 1; rm < MAX_RECENT_MINERS; ++rm) { + if (state.recentMiners[rm].deposit < state.recentMiners[lowestIx].deposit || + (state.recentMiners[rm].deposit == state.recentMiners[lowestIx].deposit && + state.recentMiners[rm].lastEntropyVersion < state.recentMiners[lowestIx].lastEntropyVersion)) + lowestIx = rm; + } + if (state.commitments[i].amount > state.recentMiners[lowestIx].deposit || + (state.commitments[i].amount == state.recentMiners[lowestIx].deposit && + state.entropyPoolVersion > state.recentMiners[lowestIx].lastEntropyVersion)) + { + state.recentMiners[lowestIx].minerId = invocatorId; + state.recentMiners[lowestIx].deposit = state.commitments[i].amount; + state.recentMiners[lowestIx].lastEntropyVersion = state.entropyPoolVersion; + state.recentMiners[lowestIx].lastRevealTick = currentTick; + } + } + } + } + // Compaction after reveal + state.totalSecurityDepositsLocked -= state.commitments[i].amount; + if (i != state.commitmentCount - 1) + state.commitments[i] = state.commitments[state.commitmentCount - 1]; + state.commitmentCount--; + // do not increment i so new moved slot is checked + continue; + } + } + i++; + } + } + + // Step 2: Process new commitment + if (hasNewCommit && !isStoppingMining) + { + if (isValidDepositAmountCheck(state, invocatorAmount) && invocatorAmount >= state.minimumSecurityDeposit) + { + if (state.commitmentCount < 1024) + { + state.commitments[state.commitmentCount].digest = inputData.committedDigest; + state.commitments[state.commitmentCount].invocatorId = invocatorId; + state.commitments[state.commitmentCount].amount = invocatorAmount; + state.commitments[state.commitmentCount].commitTick = currentTick; + state.commitments[state.commitmentCount].revealDeadlineTick = currentTick + state.revealTimeoutTicks; + state.commitments[state.commitmentCount].hasRevealed = false; + state.commitmentCount++; + state.totalCommits++; + state.totalSecurityDepositsLocked += invocatorAmount; + output.commitSuccessful = true; + } + } + } + + // Always return random bytes (current pool) + generateRandomBytesData(state, output.randomBytes, 32, 0, currentTick); // 0 = current pool + output.entropyVersion = state.entropyPoolVersion; + } + + // -------------------------------------------------- + // BUY ENTROPY / RANDOM BYTES + PUBLIC_PROCEDURE(BuyEntropy) + { + // Process timeouts first + uint32 currentTick = qpi.tick(); + for (uint32 i = 0; i < state.commitmentCount; ) { + if (!state.commitments[i].hasRevealed && + currentTick > state.commitments[i].revealDeadlineTick) + { + uint64 lostDeposit = state.commitments[i].amount; + state.lostDepositsRevenue += lostDeposit; + state.totalRevenue += lostDeposit; + state.pendingShareholderDistribution += lostDeposit; + state.totalSecurityDepositsLocked -= lostDeposit; + if (i != state.commitmentCount - 1) + state.commitments[i] = state.commitments[state.commitmentCount - 1]; + state.commitmentCount--; + } + else { + i++; + } + } + + if (qpi.numberOfTickTransactions() == -1) { + copyMemory(output, BuyEntropy_output{}); + output.success = false; + return; + } + + const BuyEntropy_input& inputData = input; + copyMemory(output, BuyEntropy_output{}); + output.success = false; + uint64 buyerFee = qpi.invocationReward(); + + bool eligible = false; + uint64 usedMinerDeposit = 0; + for (uint32 i = 0; i < state.recentMinerCount; ++i) { + if (state.recentMiners[i].deposit >= inputData.minMinerDeposit && + (currentTick - state.recentMiners[i].lastRevealTick) <= state.revealTimeoutTicks) { + eligible = true; + usedMinerDeposit = state.recentMiners[i].deposit; + break; + } + } + + if (!eligible) + return; + + uint64 minPrice = state.pricePerByte + * inputData.numberOfBytes + * (inputData.minMinerDeposit / state.priceDepositDivisor + 1); + if (buyerFee < minPrice) + return; + + uint32 histIdx = (state.entropyHistoryHead + ENTROPY_HISTORY_LEN - 2) % ENTROPY_HISTORY_LEN; + generateRandomBytesData(state, output.randomBytes, (inputData.numberOfBytes > 32 ? 32 : inputData.numberOfBytes), histIdx, currentTick); + output.entropyVersion = state.entropyPoolVersionHistory[histIdx]; + output.usedMinerDeposit = usedMinerDeposit; + output.usedPoolVersion = state.entropyPoolVersionHistory[histIdx]; + output.success = true; + uint64 half = buyerFee / 2; + state.minerEarningsPool += half; + state.shareholderEarningsPool += (buyerFee - half); + } + + // -------------------------------------------------- +// Read-only contract info + PUBLIC_FUNCTION(GetContractInfo) + { + uint32 currentTick = qpi.tick(); + + output.totalCommits = state.totalCommits; + output.totalReveals = state.totalReveals; + output.totalSecurityDepositsLocked = state.totalSecurityDepositsLocked; + output.minimumSecurityDeposit = state.minimumSecurityDeposit; + output.revealTimeoutTicks = state.revealTimeoutTicks; + output.currentTick = currentTick; + output.entropyPoolVersion = state.entropyPoolVersion; + + output.totalRevenue = state.totalRevenue; + output.pendingShareholderDistribution = state.pendingShareholderDistribution; + output.lostDepositsRevenue = state.lostDepositsRevenue; + + output.minerEarningsPool = state.minerEarningsPool; + output.shareholderEarningsPool = state.shareholderEarningsPool; + output.recentMinerCount = state.recentMinerCount; + + for (uint32 i = 0; i < 16; i++) + output.validDepositAmounts[i] = state.validDepositAmounts[i]; + + uint32 activeCount = 0; + for (uint32 i = 0; i < state.commitmentCount; i++) + if (!state.commitments[i].hasRevealed) + activeCount++; + output.activeCommitments = activeCount; + } + + PUBLIC_FUNCTION(GetUserCommitments) + { + const GetUserCommitments_input& inputData = input; + + copyMemory(output, GetUserCommitments_output{}); + uint32 userCommitmentCount = 0; + for (uint32 i = 0; i < state.commitmentCount && userCommitmentCount < 32; i++) + { + if (isEqualIdCheck(state.commitments[i].invocatorId, inputData.userId)) + { + output.commitments[userCommitmentCount].digest = state.commitments[i].digest; + output.commitments[userCommitmentCount].amount = state.commitments[i].amount; + output.commitments[userCommitmentCount].commitTick = state.commitments[i].commitTick; + output.commitments[userCommitmentCount].revealDeadlineTick = state.commitments[i].revealDeadlineTick; + output.commitments[userCommitmentCount].hasRevealed = state.commitments[i].hasRevealed; + userCommitmentCount++; + } + } + output.commitmentCount = userCommitmentCount; + } + + PUBLIC_FUNCTION(QueryPrice) + { + const QueryPrice_input& inputData = input; + output.price = state.pricePerByte + * inputData.numberOfBytes + * (inputData.minMinerDeposit / state.priceDepositDivisor +1); + } + + // -------------------------------------------------- + // Epoch End: Distribute pools + END_EPOCH() + { + // Process timeouts first + uint32 currentTick = qpi.tick(); + for (uint32 i = 0; i < state.commitmentCount; ) { + if (!state.commitments[i].hasRevealed && + currentTick > state.commitments[i].revealDeadlineTick) + { + uint64 lostDeposit = state.commitments[i].amount; + state.lostDepositsRevenue += lostDeposit; + state.totalRevenue += lostDeposit; + state.pendingShareholderDistribution += lostDeposit; + state.totalSecurityDepositsLocked -= lostDeposit; + if (i != state.commitmentCount - 1) + state.commitments[i] = state.commitments[state.commitmentCount - 1]; + state.commitmentCount--; + } + else { + i++; + } + } + + // Distribute miner pool + if (state.minerEarningsPool > 0 && state.recentMinerCount > 0) { + uint64 payout = state.minerEarningsPool / state.recentMinerCount; + for (uint32 i = 0; i < state.recentMinerCount; ++i) { + if (!isZeroIdCheck(state.recentMiners[i].minerId)) + qpi.transfer(state.recentMiners[i].minerId, payout); + } + state.minerEarningsPool = 0; + for (uint32 i = 0; i < MAX_RECENT_MINERS; ++i) + state.recentMiners[i] = RecentMiner{}; + state.recentMinerCount = 0; + } + + // Distribute to shareholders + if (state.shareholderEarningsPool > 0) { + qpi.distributeDividends(state.shareholderEarningsPool / NUMBER_OF_COMPUTORS); + state.shareholderEarningsPool = 0; + } + + // Continue current lost deposit distribution as before + if (state.pendingShareholderDistribution > 0) + { + qpi.distributeDividends(state.pendingShareholderDistribution / NUMBER_OF_COMPUTORS); + state.pendingShareholderDistribution = 0; + } } REGISTER_USER_FUNCTIONS_AND_PROCEDURES() { + // READ-ONLY USER FUNCTIONS + REGISTER_USER_FUNCTION(GetContractInfo, 1); + REGISTER_USER_FUNCTION(GetUserCommitments, 2); + REGISTER_USER_FUNCTION(QueryPrice, 3); + + // USER PROCEDURES REGISTER_USER_PROCEDURE(RevealAndCommit, 1); + REGISTER_USER_PROCEDURE(BuyEntropy, 2); } INITIALIZE() { - state._bitFee = 1000; + state.entropyHistoryHead = 0; + for (uint32 i = 0; i < ENTROPY_HISTORY_LEN; ++i) { + copyMemory(state.entropyHistory[i], m256i{}); + state.entropyPoolVersionHistory[i] = 0; + } + copyMemory(state.currentEntropyPool, m256i{}); + state.entropyPoolVersion = 0; + state.totalCommits = 0; + state.totalReveals = 0; + state.totalSecurityDepositsLocked = 0; + state.minimumSecurityDeposit = 1; // Now allow1 QU + state.revealTimeoutTicks = 9; + state.commitmentCount = 0; + state.totalRevenue = 0; + state.pendingShareholderDistribution = 0; + state.lostDepositsRevenue = 0; + state.minerEarningsPool = 0; + state.shareholderEarningsPool = 0; + state.recentMinerCount = 0; + state.pricePerByte = 10; + state.priceDepositDivisor = 1000; + for (uint32 i = 0; i < 16; i++) { + state.validDepositAmounts[i] = 1ULL; + for (uint32 j = 0; j < i; j++) + state.validDepositAmounts[i] *= 10; + } + for (uint32 i = 0; i < 1024; ++i) + state.commitments[i] = EntropyCommitment{}; + for (uint32 i = 0; i < MAX_RECENT_MINERS; ++i) + state.recentMiners[i] = RecentMiner{}; } -}; +}; \ No newline at end of file diff --git a/test/contract_random.cpp b/test/contract_random.cpp new file mode 100644 index 000000000..c692b513d --- /dev/null +++ b/test/contract_random.cpp @@ -0,0 +1,511 @@ +#define NO_UEFI + +#include +#include "contract_testing.h" + +class ContractTestingRandom : public ContractTesting +{ +public: + ContractTestingRandom() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(RANDOM); + callSystemProcedure(0, INITIALIZE); + } + + // Commit+reveal convenience + void commit(const id& miner, const bit_4096& commitBits, uint64_t deposit) + { + increaseEnergy(miner, deposit *2); // ensure enough QU + RANDOM::RevealAndCommit_input inp{}; + inp.committedDigest = k12Digest(commitBits); // Use real K12 digest for test + RANDOM::RevealAndCommit_output out{}; + invokeUserProcedure(0,1, inp, out, miner, deposit); + } + + void revealAndCommit(const id& miner, const bit_4096& revealBits, const bit_4096& newCommitBits, uint64_t deposit) + { + RANDOM::RevealAndCommit_input inp{}; + inp.revealedBits = revealBits; + inp.committedDigest = k12Digest(newCommitBits); + RANDOM::RevealAndCommit_output out{}; + invokeUserProcedure(0,1, inp, out, miner, deposit); + } + + void stopMining(const id& miner, const bit_4096& revealBits) + { + RANDOM::RevealAndCommit_input inp{}; + inp.revealedBits = revealBits; + inp.committedDigest = id::zero(); + RANDOM::RevealAndCommit_output out{}; + invokeUserProcedure(0,1, inp, out, miner,0); + } + + bool buyEntropy(const id& buyer, uint32_t numBytes, uint64_t minMinerDeposit, uint64_t suggestedFee, bool expectSuccess) + { + increaseEnergy(buyer, suggestedFee +10000); + RANDOM::BuyEntropy_input inp{}; + inp.numberOfBytes = numBytes; + inp.minMinerDeposit = minMinerDeposit; + RANDOM::BuyEntropy_output out{}; + invokeUserProcedure(0,2, inp, out, buyer, suggestedFee); + if (expectSuccess) + EXPECT_TRUE(out.success); + else + EXPECT_FALSE(out.success); + return out.success; + } + + // Direct call to get price + uint64_t queryPrice(uint32_t numBytes, uint64_t minMinerDeposit) + { + RANDOM::QueryPrice_input q{}; + q.numberOfBytes = numBytes; + q.minMinerDeposit = minMinerDeposit; + RANDOM::QueryPrice_output o{}; + callFunction(0,3, q, o); + return o.price; + } + + // Helper entropy/id for test readability + static bit_4096 testBits(uint64_t v) { + bit_4096 b{}; + uint64_t* ptr = reinterpret_cast(&b); + for (int i =0; i <64; ++i) ptr[i] = v ^ (0xDEADBEEF12340000ULL | i); + return b; + } + static id testId(uint64_t base) { + id d = id::zero(); + for (int i =0; i <32; ++i) d.m256i_u8[i] = uint8_t((base >> (i %8)) + i); + return d; + } + static id k12Digest(const bit_4096& b) { + id digest = id::zero(); + KangarooTwelve(&b, sizeof(b), &digest, sizeof(digest)); + return digest; + } +}; + +//------------------------------ +// TEST CASES +//------------------------------ + +// Helper macros for tick/time simulation +#define SET_TICK(val) (system.tick = (val)) +#define GET_TICK() (system.tick) +// To simulate an empty tick, set numberTickTransactions to -1 +#define SET_TICK_IS_EMPTY(val) (numberTickTransactions = ((val) ? -1 :0)) + +TEST(ContractRandom, BasicCommitRevealStop) +{ + ContractTestingRandom random; + id miner = ContractTestingRandom::testId(10); + bit_4096 E1 = ContractTestingRandom::testBits(101); + bit_4096 E2 = ContractTestingRandom::testBits(202); + + random.commit(miner, E1, 1000); + random.revealAndCommit(miner, E1, E2, 1000); + random.stopMining(miner, E2); + + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); +} + +TEST(ContractRandom, TimeoutsAndRefunds) +{ + ContractTestingRandom random; + id miner = ContractTestingRandom::testId(11); + bit_4096 bits = ContractTestingRandom::testBits(303); + + random.commit(miner, bits, 2000); + + // Timeout: Advance tick past deadline + RANDOM::GetContractInfo_input ci0{}; + RANDOM::GetContractInfo_output co0{}; + random.callFunction(0, 1, ci0, co0); + int timeoutTick = GET_TICK() + co0.revealTimeoutTicks + 1; + SET_TICK(timeoutTick); + + // Trigger timeout (choose any call, including another commit or dummy reveal) + RANDOM::RevealAndCommit_input dummy = {}; + RANDOM::RevealAndCommit_output out{}; + random.invokeUserProcedure(0, 1, dummy, out, miner, 0); + + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); + EXPECT_EQ(co.lostDepositsRevenue, 2000); +} + +TEST(ContractRandom, EmptyTickRefund) +{ + ContractTestingRandom random; + id miner = ContractTestingRandom::testId(12); + bit_4096 bits = ContractTestingRandom::testBits(404); + + random.commit(miner, bits, 3000); + + // Use GetContractInfo to get revealTimeoutTicks + RANDOM::GetContractInfo_input ci0{}; + RANDOM::GetContractInfo_output co0{}; + random.callFunction(0, 1, ci0, co0); + int refundTick = system.tick + co0.revealTimeoutTicks; + system.tick = refundTick; + numberTickTransactions = -1; + + // All deadlines expire on an empty tick: refund + RANDOM::RevealAndCommit_input dummy = {}; + RANDOM::RevealAndCommit_output out{}; + random.invokeUserProcedure(0, 1, dummy, out, miner, 0); + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); +} + +TEST(ContractRandom, BuyEntropyEligibility) +{ + ContractTestingRandom random; + id miner = ContractTestingRandom::testId(13); + id buyer = ContractTestingRandom::testId(14); + bit_4096 bits = ContractTestingRandom::testBits(321); + + // No miners: should fail + EXPECT_FALSE(random.buyEntropy(buyer, 8, 1000, 8000, false)); + + // Commit/reveal + random.commit(miner, bits, 1000); + random.revealAndCommit(miner, bits, ContractTestingRandom::testBits(333), 1000); + + // Should now succeed + EXPECT_TRUE(random.buyEntropy(buyer, 16, 1000, 16000, true)); + + // Advance past freshness window, should fail again + RANDOM::GetContractInfo_input ci0{}; + RANDOM::GetContractInfo_output co0{}; + random.callFunction(0, 1, ci0, co0); + system.tick = system.tick + co0.revealTimeoutTicks + 1; + EXPECT_FALSE(random.buyEntropy(buyer, 16, 1000, 16000, false)); +} + +TEST(ContractRandom, QueryPriceLogic) +{ + ContractTestingRandom random; + RANDOM::QueryPrice_input q{}; + q.numberOfBytes = 16; + q.minMinerDeposit = 1000; + RANDOM::QueryPrice_output o{}; + random.callFunction(0, 3, q, o); + uint64_t price = random.queryPrice(16, 1000); + EXPECT_EQ(price, o.price); +} + +TEST(ContractRandom, CompactionBehavior) +{ + ContractTestingRandom random; + for (int i = 0; i < 10; ++i) { + id miner = ContractTestingRandom::testId(100 + i); + bit_4096 bits = ContractTestingRandom::testBits(1001 + i); + random.commit(miner, bits, 5000); + random.revealAndCommit(miner, bits, ContractTestingRandom::testBits(2001 + i), 5000); + random.stopMining(miner, ContractTestingRandom::testBits(2001 + i)); + } +} + +TEST(ContractRandom, MultipleMinersAndBuyers) +{ + ContractTestingRandom random; + id minerA = ContractTestingRandom::testId(1001); + id minerB = ContractTestingRandom::testId(1002); + id buyer1 = ContractTestingRandom::testId(1003); + id buyer2 = ContractTestingRandom::testId(1004); + bit_4096 entropyA = ContractTestingRandom::testBits(5678); + bit_4096 entropyB = ContractTestingRandom::testBits(6789); + + // Both miners commit, same deposit + random.commit(minerA, entropyA, 10000); + random.commit(minerB, entropyB, 10000); + random.revealAndCommit(minerA, entropyA, ContractTestingRandom::testBits(8888), 10000); + random.revealAndCommit(minerB, entropyB, ContractTestingRandom::testBits(9999), 10000); + + // Buyer1 can purchase with either miner as eligible + EXPECT_TRUE(random.buyEntropy(buyer1, 8, 10000, 20000, true)); + // Buyer2 requires more security than available + EXPECT_FALSE(random.buyEntropy(buyer2, 16, 20000, 35000, false)); +} + +TEST(ContractRandom, MaxCommitmentsAndEviction) +{ + ContractTestingRandom random; + // Fill the commitments array + const int N = 32; + std::vector miners; + for (int i = 0; i < N; ++i) { + miners.push_back(ContractTestingRandom::testId(300 + i)); + random.commit(miners.back(), ContractTestingRandom::testBits(1234 + i), 5555); + } + + // Reveal all out-of-order, ensure compaction + for (int i = N - 1; i >= 0; --i) { + random.revealAndCommit(miners[i], ContractTestingRandom::testBits(1234 + i), ContractTestingRandom::testBits(2000 + i), 5555); + random.stopMining(miners[i], ContractTestingRandom::testBits(2000 + i)); + } +} + +TEST(ContractRandom, EndEpochDistribution) +{ + ContractTestingRandom random; + id miner1 = ContractTestingRandom::testId(99); + id miner2 = ContractTestingRandom::testId(98); + bit_4096 e1 = ContractTestingRandom::testBits(501); + bit_4096 e2 = ContractTestingRandom::testBits(502); + + random.commit(miner1, e1, 10000); + random.revealAndCommit(miner1, e1, ContractTestingRandom::testBits(601), 10000); + random.commit(miner2, e2, 10000); + random.revealAndCommit(miner2, e2, ContractTestingRandom::testBits(602), 10000); + + id buyer = ContractTestingRandom::testId(90); + uint64_t price = random.queryPrice(16, 10000); + random.buyEntropy(buyer, 16, 10000, price, true); + + // Simulate EndEpoch + random.callSystemProcedure(0, END_EPOCH); + + // After epoch, earnings pools should be zeroed and recentMinerCount cleared + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.minerEarningsPool, 0); + EXPECT_EQ(co.shareholderEarningsPool, 0); + EXPECT_EQ(co.recentMinerCount, 0); +} + +TEST(ContractRandom, RecentMinerEvictionPolicy) +{ + ContractTestingRandom random; + const int maxMiners = MAX_RECENT_MINERS; + std::vector miners; + auto baseDeposit = 1000; + + // Fill up to MAX_RECENT_MINERS, all with same deposit + for (int i = 0; i < maxMiners; ++i) { + auto miner = ContractTestingRandom::testId(5000 + i); + miners.push_back(miner); + random.commit(miner, random.testBits(7000 + i), baseDeposit); + random.revealAndCommit(miner, random.testBits(7000 + i), random.testBits(8000 + i), baseDeposit); + } + RANDOM::GetContractInfo_input ci0{}; + RANDOM::GetContractInfo_output co0{}; + random.callFunction(0, 1, ci0, co0); + EXPECT_EQ(co0.recentMinerCount, maxMiners); + + // Add new miner with higher deposit, should evict one of the previous (lowest deposit) + id highMiner = ContractTestingRandom::testId(99999); + random.commit(highMiner, random.testBits(55555), baseDeposit * 10); + random.revealAndCommit(highMiner, random.testBits(55555), random.testBits(55566), baseDeposit * 10); + + RANDOM::GetContractInfo_input ci1{}; + RANDOM::GetContractInfo_output co1{}; + random.callFunction(0, 1, ci1, co1); + EXPECT_EQ(co1.recentMinerCount, maxMiners); + + // All lower deposit miners except one likely evicted, highMiner should be present with max deposit + int foundHigh = 0; + RANDOM::GetUserCommitments_input inp{}; + RANDOM::GetUserCommitments_output out{}; + for (uint32_t i = 0; i < maxMiners; ++i) { + inp.userId = highMiner; + random.callFunction(0, 2, inp, out); + if (out.commitmentCount > 0) foundHigh++; + } + EXPECT_EQ(foundHigh, 1); +} + +TEST(ContractRandom, BuyerPickinessHighRequirement) +{ + ContractTestingRandom random; + id miner = random.testId(721); + id buyer = random.testId(722); + uint64_t lowDeposit = 1000, highDeposit = 100000; + + random.commit(miner, random.testBits(100), lowDeposit); + random.revealAndCommit(miner, random.testBits(100), random.testBits(101), lowDeposit); + + // As buyer, require higher min deposit than any available miner supplied + EXPECT_FALSE(random.buyEntropy(buyer, 8, highDeposit, 10000, false)); +} + +TEST(ContractRandom, MixedDepositLevels) +{ + ContractTestingRandom random; + id lowMiner = random.testId(1001); + id highMiner = random.testId(1002); + id buyer = random.testId(1003); + + random.commit(lowMiner, random.testBits(88), 1000); + random.commit(highMiner, random.testBits(89), 100000); + random.revealAndCommit(lowMiner, random.testBits(88), random.testBits(188), 1000); + random.revealAndCommit(highMiner, random.testBits(89), random.testBits(189), 100000); + + EXPECT_TRUE(random.buyEntropy(buyer, 8, 1000, 10000, true)); + EXPECT_TRUE(random.buyEntropy(buyer, 8, 100000, 100000, true)); + EXPECT_FALSE(random.buyEntropy(buyer, 8, 100001, 100000, false)); +} + +TEST(ContractRandom, EmptyTickRefund_MultiMiners) +{ + ContractTestingRandom random; + id m1 = random.testId(931); + id m2 = random.testId(932); + random.commit(m1, random.testBits(401), 5000); + random.commit(m2, random.testBits(402), 7000); + + RANDOM::GetContractInfo_input ci0{}; + RANDOM::GetContractInfo_output co0{}; + random.callFunction(0, 1, ci0, co0); + int tick = system.tick + co0.revealTimeoutTicks; + system.tick = tick; + numberTickTransactions = -1; + + RANDOM::RevealAndCommit_input dummy = {}; + RANDOM::RevealAndCommit_output out{}; + random.invokeUserProcedure(0, 1, dummy, out, m1, 0); + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); + // test both miners' balances for refund if desired +} + +TEST(ContractRandom, Timeout_MultiMiners) +{ + ContractTestingRandom random; + id m1 = random.testId(7777); + id m2 = random.testId(8888); + random.commit(m1, random.testBits(111), 2000); + random.commit(m2, random.testBits(112), 4000); + RANDOM::GetContractInfo_input ci0{}; + RANDOM::GetContractInfo_output co0{}; + random.callFunction(0, 1, ci0, co0); + int afterTimeout = system.tick + co0.revealTimeoutTicks + 1; + system.tick = afterTimeout; + + RANDOM::RevealAndCommit_input dummy = {}; + RANDOM::RevealAndCommit_output out{}; + random.invokeUserProcedure(0, 1, dummy, out, m2, 0); + + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); + EXPECT_EQ(co.lostDepositsRevenue, 6000); +} + +TEST(ContractRandom, MultipleBuyersEpochReset) +{ + ContractTestingRandom random; + id miner = random.testId(1201); + id buyer1 = random.testId(1301); + id buyer2 = random.testId(1401); + + random.commit(miner, random.testBits(900), 8000); + random.revealAndCommit(miner, random.testBits(900), random.testBits(901), 8000); + + EXPECT_TRUE(random.buyEntropy(buyer1, 8, 8000, 20000, true)); + EXPECT_TRUE(random.buyEntropy(buyer2, 16, 8000, 50000, true)); + + random.callSystemProcedure(0, END_EPOCH); + + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.minerEarningsPool, 0); + EXPECT_EQ(co.shareholderEarningsPool, 0); + EXPECT_EQ(co.recentMinerCount, 0); +} + +TEST(ContractRandom, QueryUserCommitmentsInfo) +{ + ContractTestingRandom random; + id miner = random.testId(2001); + + random.commit(miner, random.testBits(1234), 10000); + + // Call GetUserCommitments for miner + RANDOM::GetUserCommitments_input inp{}; + inp.userId = miner; + RANDOM::GetUserCommitments_output out{}; + random.callFunction(0, 2, inp, out); + EXPECT_GE(out.commitmentCount, 1); + + // Call GetContractInfo for global stats + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_GE(co.totalCommits, 1); +} + +TEST(ContractRandom, RejectInvalidDeposits) +{ + ContractTestingRandom random; + id miner = random.testId(2012); + + // Try to commit invalid deposit (not a power of ten) + RANDOM::RevealAndCommit_input inp{}; + inp.committedDigest = ContractTestingRandom::k12Digest(ContractTestingRandom::testBits(66)); + RANDOM::RevealAndCommit_output out{}; + // Use7777 which is not a power of ten, should not register a commitment + random.invokeUserProcedure(0, 1, inp, out, miner, 7777); + + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); +} + +TEST(ContractRandom, BuyEntropyEdgeNumBytes) +{ + ContractTestingRandom random; + id miner = random.testId(3031); + id buyer = random.testId(3032); + + random.commit(miner, random.testBits(8888), 8000); + random.revealAndCommit(miner, random.testBits(8888), random.testBits(8899), 8000); + + //1 byte (minimum) + EXPECT_TRUE(random.buyEntropy(buyer, 1, 8000, 10000, true)); + //32 bytes (maximum) + EXPECT_TRUE(random.buyEntropy(buyer, 32, 8000, 40000, true)); + //33 bytes (over contract max, should clamp or fail) + EXPECT_FALSE(random.buyEntropy(buyer, 33, 8000, 50000, false)); +} + +TEST(ContractRandom, OutOfOrderRevealAndCompaction) +{ + ContractTestingRandom random; + std::vector miners; + for (int i = 0; i < 8; ++i) { + miners.push_back(random.testId(5400 + i)); + random.commit(miners.back(), random.testBits(8500 + i), 6000); + } + // Reveal/stop in random order + random.revealAndCommit(miners[3], random.testBits(8500 + 3), random.testBits(9500 + 3), 6000); + random.revealAndCommit(miners[1], random.testBits(8500 + 1), random.testBits(9500 + 1), 6000); + random.stopMining(miners[3], random.testBits(9500 + 3)); + random.stopMining(miners[1], random.testBits(9500 + 1)); + // Now reveal/stop remainder + for (int i = 0; i < 8; ++i) { + if (i == 1 || i == 3) continue; + random.revealAndCommit(miners[i], random.testBits(8500 + i), random.testBits(9500 + i), 6000); + random.stopMining(miners[i], random.testBits(9500 + i)); + } + RANDOM::GetContractInfo_input ci{}; + RANDOM::GetContractInfo_output co{}; + random.callFunction(0, 1, ci, co); + EXPECT_EQ(co.activeCommitments, 0); +} \ No newline at end of file diff --git a/test/test.vcxproj b/test/test.vcxproj index 199307382..2e317a8d1 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -122,6 +122,7 @@ + diff --git a/test/test.vcxproj.filters b/test/test.vcxproj.filters index ab3d0b8f9..cd4d71a6e 100644 --- a/test/test.vcxproj.filters +++ b/test/test.vcxproj.filters @@ -42,6 +42,7 @@ + From 7e8e3a8470c3c651aadc7d95e6b98237408028a2 Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Thu, 11 Dec 2025 09:12:08 +0100 Subject: [PATCH 02/12] Refactor to N-010 suggestions --- src/contracts/Random.h | 496 ++++++++++++++++++++++------------------- 1 file changed, 270 insertions(+), 226 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index c6d1569c5..bbe8c2fc6 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -1,8 +1,8 @@ using namespace QPI; -// Max miners to consider for distribution -#define MAX_RECENT_MINERS 369 -#define ENTROPY_HISTORY_LEN 3 // For 2-tick-back entropy pool +// Prefer constexpr over #define +constexpr uint32_t MAX_RECENT_MINERS = 369; +constexpr uint32_t ENTROPY_HISTORY_LEN = 3; // For 2-tick-back entropy pool struct RANDOM2 { @@ -66,16 +66,17 @@ struct RANDOM : public ContractBase uint32 commitmentCount; // Helper functions (static inline) + // Remove local variables, pass as arguments if needed static inline void updateEntropyPoolData(RANDOM& stateRef, const bit_4096& newEntropy) { - // XOR new entropy into the global pool - // Access BitArray internal data through a uint64 pointer cast const uint64* entropyData = reinterpret_cast(&newEntropy); + for (uint32 i = 0; i < 4; i++) + { stateRef.currentEntropyPool.m256i_u64[i] ^= entropyData[i]; + } - // Update entropy history (circular buffer) - stateRef.entropyHistoryHead = (stateRef.entropyHistoryHead + 1U) % ENTROPY_HISTORY_LEN; + stateRef.entropyHistoryHead = mod(stateRef.entropyHistoryHead + 1U, ENTROPY_HISTORY_LEN); stateRef.entropyHistory[stateRef.entropyHistoryHead] = stateRef.currentEntropyPool; stateRef.entropyPoolVersion++; stateRef.entropyPoolVersionHistory[stateRef.entropyHistoryHead] = stateRef.entropyPoolVersion; @@ -83,58 +84,67 @@ struct RANDOM : public ContractBase static inline void generateRandomBytesData(const RANDOM& stateRef, uint8* output, uint32 numBytes, uint32 historyIdx, uint32 currentTick) { - const m256i selectedPool = stateRef.entropyHistory[(stateRef.entropyHistoryHead + ENTROPY_HISTORY_LEN - historyIdx) % ENTROPY_HISTORY_LEN]; + const m256i selectedPool = stateRef.entropyHistory[mod(stateRef.entropyHistoryHead + ENTROPY_HISTORY_LEN - historyIdx, ENTROPY_HISTORY_LEN)]; m256i tickEntropy; tickEntropy.m256i_u64[0] = static_cast(currentTick); tickEntropy.m256i_u64[1] = 0; tickEntropy.m256i_u64[2] = 0; tickEntropy.m256i_u64[3] = 0; m256i combinedEntropy; + for (uint32 i = 0; i < 4; i++) + { combinedEntropy.m256i_u64[i] = selectedPool.m256i_u64[i] ^ tickEntropy.m256i_u64[i]; + } - // Copy bytes from the combined entropy to output for (uint32 i = 0; i < ((numBytes > 32U) ? 32U : numBytes); i++) + { output[i] = combinedEntropy.m256i_u8[i]; + } } static inline bool isValidDepositAmountCheck(const RANDOM& stateRef, uint64 amount) { for (uint32 i = 0; i < 16U; i++) - if (amount == stateRef.validDepositAmounts[i]) return true; + { + if (amount == stateRef.validDepositAmounts[i]) + { + return true; + } + } return false; } - static inline bool isEqualIdCheck(const id& a, const id& b) - { - for (uint32 i = 0; i < 32U; i++) - if (a.m256i_u8[i] != b.m256i_u8[i]) return false; - return true; + static inline bool isEqualIdCheck(const id& a, const id& b) + { + return a == b; } - static inline bool isZeroIdCheck(const id& value) - { - for (uint32 i = 0; i < 32U; i++) - if (value.m256i_u8[i] != 0) return false; - return true; + static inline bool isZeroIdCheck(const id& value) + { + return isZero(value); } static inline bool isZeroBitsCheck(const bit_4096& value) { - // Access BitArray internal data through a uint64 pointer cast const uint64* data = reinterpret_cast(&value); - for (uint32 i = 0; i < 64U; i++) - if (data[i] != 0) return false; + for (uint32 i = 0; i < 64U; i++) { + if (data[i] != 0) + { + return false; + } + } + return true; } - // Helper functions (static inline) static inline bool k12CommitmentMatches(const QPI::QpiContextFunctionCall& qpi, const QPI::bit_4096& revealedBits, const QPI::id& committedDigest) { - // QPI K12 returns id (m256i) QPI::id computedDigest = qpi.K12(revealedBits); for (QPI::uint32 i = 0; i < 32U; i++) + { if (computedDigest.m256i_u8[i] != committedDigest.m256i_u8[i]) return false; + } return true; } @@ -227,75 +237,99 @@ struct RANDOM : public ContractBase uint64 price; }; + // Locals structs for WITH_LOCALS macros + struct RevealAndCommit_locals { + uint32 currentTick; + bool hasRevealData; + bool hasNewCommit; + bool isStoppingMining; + sint32 existingIndex; + uint32 i; + uint32 rm; + uint32 lowestIx; + bool hashMatches; + }; + struct BuyEntropy_locals { + uint32 currentTick; + bool eligible; + uint64 usedMinerDeposit; + uint32 i; + uint64 minPrice; + uint64 buyerFee; + uint32 histIdx; + uint64 half; + }; + struct END_EPOCH_locals { + uint32 currentTick; + uint32 i; + uint64 payout; + }; + // -------------------------------------------------- - // Mining: RevealAndCommit - PUBLIC_PROCEDURE(RevealAndCommit) + // Mining: RevealAndCommit + PUBLIC_PROCEDURE_WITH_LOCALS(RevealAndCommit) { - // Process timeouts first - uint32 currentTick = qpi.tick(); - for (uint32 i = 0; i < state.commitmentCount; ) { - if (!state.commitments[i].hasRevealed && - currentTick > state.commitments[i].revealDeadlineTick) + locals.currentTick = qpi.tick(); + + for (locals.i = 0; locals.i < state.commitmentCount; ) + { + if (!state.commitments[locals.i].hasRevealed && + locals.currentTick > state.commitments[locals.i].revealDeadlineTick) { - uint64 lostDeposit = state.commitments[i].amount; + uint64 lostDeposit = state.commitments[locals.i].amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; state.totalSecurityDepositsLocked -= lostDeposit; - if (i != state.commitmentCount - 1) - state.commitments[i] = state.commitments[state.commitmentCount - 1]; + if (locals.i != state.commitmentCount - 1) + state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; state.commitmentCount--; } - else { - i++; + else + { + locals.i++; } } - // Empty tick handling: - if (qpi.numberOfTickTransactions() == -1) { - for (uint32 i = 0; i < state.commitmentCount; ) { - if (!state.commitments[i].hasRevealed && - state.commitments[i].revealDeadlineTick == qpi.tick()) + if (qpi.numberOfTickTransactions() == -1) + { + for (locals.i = 0; locals.i < state.commitmentCount; ) + { + if (!state.commitments[locals.i].hasRevealed && + state.commitments[locals.i].revealDeadlineTick == qpi.tick()) { - qpi.transfer(state.commitments[i].invocatorId, state.commitments[i].amount); - state.totalSecurityDepositsLocked -= state.commitments[i].amount; - // Remove this slot by moving last in - if (i != state.commitmentCount - 1) - state.commitments[i] = state.commitments[state.commitmentCount - 1]; + qpi.transfer(state.commitments[locals.i].invocatorId, state.commitments[locals.i].amount); + state.totalSecurityDepositsLocked -= state.commitments[locals.i].amount; + if (locals.i != state.commitmentCount - 1) + state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; state.commitmentCount--; - // Do not increment i, so the moved entry is checked next } - else { - i++; + else + { + locals.i++; } } - copyMemory(output, RevealAndCommit_output{}); return; } - const RevealAndCommit_input& inputData = input; - id invocatorId = qpi.invocator(); - uint64 invocatorAmount = qpi.invocationReward(); - copyMemory(output, RevealAndCommit_output{}); + locals.hasRevealData = !isZeroBitsCheck(input.revealedBits); + locals.hasNewCommit = !isZeroIdCheck(input.committedDigest); + locals.isStoppingMining = (qpi.invocationReward() == 0); - bool hasRevealData = !isZeroBitsCheck(inputData.revealedBits); - bool hasNewCommit = !isZeroIdCheck(inputData.committedDigest); - bool isStoppingMining = (invocatorAmount == 0); - - // Step 1: Process reveal if provided - if (hasRevealData) + if (locals.hasRevealData) { - for (uint32 i = 0; i < state.commitmentCount; ) { - if (!state.commitments[i].hasRevealed && - isEqualIdCheck(state.commitments[i].invocatorId, invocatorId)) + for (locals.i = 0; locals.i < state.commitmentCount; ) + { + if (!state.commitments[locals.i].hasRevealed && + isEqualIdCheck(state.commitments[locals.i].invocatorId, qpi.invocator())) { - // Use QPI K12 for cryptographic binding - bool hashMatches = k12CommitmentMatches(qpi, inputData.revealedBits, state.commitments[i].digest); - if (hashMatches) + locals.hashMatches = k12CommitmentMatches(qpi, input.revealedBits, state.commitments[locals.i].digest); + + if (locals.hashMatches) { - if (currentTick > state.commitments[i].revealDeadlineTick) + if (locals.currentTick > state.commitments[locals.i].revealDeadlineTick) { - uint64 lostDeposit = state.commitments[i].amount; + uint64 lostDeposit = state.commitments[locals.i].amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; @@ -303,170 +337,190 @@ struct RANDOM : public ContractBase } else { - updateEntropyPoolData(state, inputData.revealedBits); - - qpi.transfer(invocatorId, state.commitments[i].amount); - + updateEntropyPoolData(state, input.revealedBits); + qpi.transfer(qpi.invocator(), state.commitments[locals.i].amount); output.revealSuccessful = true; - output.depositReturned = state.commitments[i].amount; - + output.depositReturned = state.commitments[locals.i].amount; state.totalReveals++; - state.totalSecurityDepositsLocked -= state.commitments[i].amount; - - // Update RecentMiners (with per-miner freshness) - sint32 existingIndex = -1; - for (uint32 rm = 0; rm < state.recentMinerCount; ++rm) { - if (isEqualIdCheck(state.recentMiners[rm].minerId, invocatorId)) { - existingIndex = rm; + state.totalSecurityDepositsLocked -= state.commitments[locals.i].amount; + locals.existingIndex = -1; + + for (locals.rm = 0; locals.rm < state.recentMinerCount; ++locals.rm) + { + if (isEqualIdCheck(state.recentMiners[locals.rm].minerId, qpi.invocator())) + { + locals.existingIndex = locals.rm; break; } } - if (existingIndex >= 0) { - if (state.recentMiners[existingIndex].deposit < state.commitments[i].amount) { - state.recentMiners[existingIndex].deposit = state.commitments[i].amount; - state.recentMiners[existingIndex].lastEntropyVersion = state.entropyPoolVersion; + + if (locals.existingIndex >= 0) + { + if (state.recentMiners[locals.existingIndex].deposit < state.commitments[locals.i].amount) + { + state.recentMiners[locals.existingIndex].deposit = state.commitments[locals.i].amount; + state.recentMiners[locals.existingIndex].lastEntropyVersion = state.entropyPoolVersion; } - state.recentMiners[existingIndex].lastRevealTick = currentTick; + state.recentMiners[locals.existingIndex].lastRevealTick = locals.currentTick; } - else { - if (state.recentMinerCount < MAX_RECENT_MINERS) { - state.recentMiners[state.recentMinerCount].minerId = invocatorId; - state.recentMiners[state.recentMinerCount].deposit = state.commitments[i].amount; + else + { + if (state.recentMinerCount < MAX_RECENT_MINERS) + { + state.recentMiners[state.recentMinerCount].minerId = qpi.invocator(); + state.recentMiners[state.recentMinerCount].deposit = state.commitments[locals.i].amount; state.recentMiners[state.recentMinerCount].lastEntropyVersion = state.entropyPoolVersion; - state.recentMiners[state.recentMinerCount].lastRevealTick = currentTick; + state.recentMiners[state.recentMinerCount].lastRevealTick = locals.currentTick; state.recentMinerCount++; } - else { - // Overflow: evict - uint32 lowestIx = 0; - for (uint32 rm = 1; rm < MAX_RECENT_MINERS; ++rm) { - if (state.recentMiners[rm].deposit < state.recentMiners[lowestIx].deposit || - (state.recentMiners[rm].deposit == state.recentMiners[lowestIx].deposit && - state.recentMiners[rm].lastEntropyVersion < state.recentMiners[lowestIx].lastEntropyVersion)) - lowestIx = rm; + else + { + locals.lowestIx = 0; + + for (locals.rm = 1; locals.rm < MAX_RECENT_MINERS; ++locals.rm) + { + if (state.recentMiners[locals.rm].deposit < state.recentMiners[locals.lowestIx].deposit || + (state.recentMiners[locals.rm].deposit == state.recentMiners[locals.lowestIx].deposit && + state.recentMiners[locals.rm].lastEntropyVersion < state.recentMiners[locals.lowestIx].lastEntropyVersion)) + { + locals.lowestIx = locals.rm; + } } - if (state.commitments[i].amount > state.recentMiners[lowestIx].deposit || - (state.commitments[i].amount == state.recentMiners[lowestIx].deposit && - state.entropyPoolVersion > state.recentMiners[lowestIx].lastEntropyVersion)) + + if (state.commitments[locals.i].amount > state.recentMiners[locals.lowestIx].deposit || + (state.commitments[locals.i].amount == state.recentMiners[locals.lowestIx].deposit && + state.entropyPoolVersion > state.recentMiners[locals.lowestIx].lastEntropyVersion)) { - state.recentMiners[lowestIx].minerId = invocatorId; - state.recentMiners[lowestIx].deposit = state.commitments[i].amount; - state.recentMiners[lowestIx].lastEntropyVersion = state.entropyPoolVersion; - state.recentMiners[lowestIx].lastRevealTick = currentTick; + state.recentMiners[locals.lowestIx].minerId = qpi.invocator(); + state.recentMiners[locals.lowestIx].deposit = state.commitments[locals.i].amount; + state.recentMiners[locals.lowestIx].lastEntropyVersion = state.entropyPoolVersion; + state.recentMiners[locals.lowestIx].lastRevealTick = locals.currentTick; } } } } - // Compaction after reveal - state.totalSecurityDepositsLocked -= state.commitments[i].amount; - if (i != state.commitmentCount - 1) - state.commitments[i] = state.commitments[state.commitmentCount - 1]; + + state.totalSecurityDepositsLocked -= state.commitments[locals.i].amount; + + if (locals.i != state.commitmentCount - 1) + { + state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + } state.commitmentCount--; - // do not increment i so new moved slot is checked + continue; } } - i++; + locals.i++; } } - // Step 2: Process new commitment - if (hasNewCommit && !isStoppingMining) + if (locals.hasNewCommit && !locals.isStoppingMining) { - if (isValidDepositAmountCheck(state, invocatorAmount) && invocatorAmount >= state.minimumSecurityDeposit) + if (isValidDepositAmountCheck(state, qpi.invocationReward()) && qpi.invocationReward() >= state.minimumSecurityDeposit) { if (state.commitmentCount < 1024) { - state.commitments[state.commitmentCount].digest = inputData.committedDigest; - state.commitments[state.commitmentCount].invocatorId = invocatorId; - state.commitments[state.commitmentCount].amount = invocatorAmount; - state.commitments[state.commitmentCount].commitTick = currentTick; - state.commitments[state.commitmentCount].revealDeadlineTick = currentTick + state.revealTimeoutTicks; + state.commitments[state.commitmentCount].digest = input.committedDigest; + state.commitments[state.commitmentCount].invocatorId = qpi.invocator(); + state.commitments[state.commitmentCount].amount = qpi.invocationReward(); + state.commitments[state.commitmentCount].commitTick = locals.currentTick; + state.commitments[state.commitmentCount].revealDeadlineTick = locals.currentTick + state.revealTimeoutTicks; state.commitments[state.commitmentCount].hasRevealed = false; state.commitmentCount++; state.totalCommits++; - state.totalSecurityDepositsLocked += invocatorAmount; + state.totalSecurityDepositsLocked += qpi.invocationReward(); output.commitSuccessful = true; } } } - // Always return random bytes (current pool) - generateRandomBytesData(state, output.randomBytes, 32, 0, currentTick); // 0 = current pool + generateRandomBytesData(state, output.randomBytes, 32, 0, locals.currentTick); output.entropyVersion = state.entropyPoolVersion; } - // -------------------------------------------------- - // BUY ENTROPY / RANDOM BYTES - PUBLIC_PROCEDURE(BuyEntropy) + // BUY ENTROPY / RANDOM BYTES + PUBLIC_PROCEDURE_WITH_LOCALS(BuyEntropy) { - // Process timeouts first - uint32 currentTick = qpi.tick(); - for (uint32 i = 0; i < state.commitmentCount; ) { - if (!state.commitments[i].hasRevealed && - currentTick > state.commitments[i].revealDeadlineTick) + locals.currentTick = qpi.tick(); + + for (locals.i = 0; locals.i < state.commitmentCount; ) + { + if (!state.commitments[locals.i].hasRevealed && + locals.currentTick > state.commitments[locals.i].revealDeadlineTick) { - uint64 lostDeposit = state.commitments[i].amount; + uint64 lostDeposit = state.commitments[locals.i].amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; state.totalSecurityDepositsLocked -= lostDeposit; - if (i != state.commitmentCount - 1) - state.commitments[i] = state.commitments[state.commitmentCount - 1]; + if (locals.i != state.commitmentCount - 1) + { + state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + } state.commitmentCount--; } - else { - i++; + else + { + locals.i++; } } - if (qpi.numberOfTickTransactions() == -1) { - copyMemory(output, BuyEntropy_output{}); + if (qpi.numberOfTickTransactions() == -1) + { output.success = false; return; } - const BuyEntropy_input& inputData = input; - copyMemory(output, BuyEntropy_output{}); output.success = false; - uint64 buyerFee = qpi.invocationReward(); - - bool eligible = false; - uint64 usedMinerDeposit = 0; - for (uint32 i = 0; i < state.recentMinerCount; ++i) { - if (state.recentMiners[i].deposit >= inputData.minMinerDeposit && - (currentTick - state.recentMiners[i].lastRevealTick) <= state.revealTimeoutTicks) { - eligible = true; - usedMinerDeposit = state.recentMiners[i].deposit; + locals.buyerFee = qpi.invocationReward(); + locals.eligible = false; + locals.usedMinerDeposit = 0; + + for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) + { + if (state.recentMiners[locals.i].deposit >= input.minMinerDeposit && + (locals.currentTick - state.recentMiners[locals.i].lastRevealTick) <= state.revealTimeoutTicks) + { + locals.eligible = true; + locals.usedMinerDeposit = state.recentMiners[locals.i].deposit; break; } } - if (!eligible) + if (!locals.eligible) + { return; + } + + locals.minPrice = state.pricePerByte + * input.numberOfBytes + * (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); - uint64 minPrice = state.pricePerByte - * inputData.numberOfBytes - * (inputData.minMinerDeposit / state.priceDepositDivisor + 1); - if (buyerFee < minPrice) + if (locals.buyerFee < locals.minPrice) + { return; + } - uint32 histIdx = (state.entropyHistoryHead + ENTROPY_HISTORY_LEN - 2) % ENTROPY_HISTORY_LEN; - generateRandomBytesData(state, output.randomBytes, (inputData.numberOfBytes > 32 ? 32 : inputData.numberOfBytes), histIdx, currentTick); - output.entropyVersion = state.entropyPoolVersionHistory[histIdx]; - output.usedMinerDeposit = usedMinerDeposit; - output.usedPoolVersion = state.entropyPoolVersionHistory[histIdx]; + locals.histIdx = mod(state.entropyHistoryHead + ENTROPY_HISTORY_LEN - 2, ENTROPY_HISTORY_LEN); + generateRandomBytesData(state, output.randomBytes, (input.numberOfBytes > 32 ? 32 : input.numberOfBytes), locals.histIdx, locals.currentTick); + output.entropyVersion = state.entropyPoolVersionHistory[locals.histIdx]; + output.usedMinerDeposit = locals.usedMinerDeposit; + output.usedPoolVersion = state.entropyPoolVersionHistory[locals.histIdx]; output.success = true; - uint64 half = buyerFee / 2; - state.minerEarningsPool += half; - state.shareholderEarningsPool += (buyerFee - half); + + locals.half = div(locals.buyerFee, (uint64)2); + state.minerEarningsPool += locals.half; + state.shareholderEarningsPool += (locals.buyerFee - locals.half); } // -------------------------------------------------- -// Read-only contract info + // Read-only contract info PUBLIC_FUNCTION(GetContractInfo) { uint32 currentTick = qpi.tick(); + uint32 activeCount = 0; + uint32 i; output.totalCommits = state.totalCommits; output.totalReveals = state.totalReveals; @@ -484,25 +538,29 @@ struct RANDOM : public ContractBase output.shareholderEarningsPool = state.shareholderEarningsPool; output.recentMinerCount = state.recentMinerCount; - for (uint32 i = 0; i < 16; i++) + for (i = 0; i < 16; i++) + { output.validDepositAmounts[i] = state.validDepositAmounts[i]; + } - uint32 activeCount = 0; - for (uint32 i = 0; i < state.commitmentCount; i++) + for (i = 0; i < state.commitmentCount; i++) + { if (!state.commitments[i].hasRevealed) + { activeCount++; + } + } output.activeCommitments = activeCount; } PUBLIC_FUNCTION(GetUserCommitments) { - const GetUserCommitments_input& inputData = input; - - copyMemory(output, GetUserCommitments_output{}); uint32 userCommitmentCount = 0; - for (uint32 i = 0; i < state.commitmentCount && userCommitmentCount < 32; i++) + uint32 i; + + for (i = 0; i < state.commitmentCount && userCommitmentCount < 32; i++) { - if (isEqualIdCheck(state.commitments[i].invocatorId, inputData.userId)) + if (isEqualIdCheck(state.commitments[i].invocatorId, input.userId)) { output.commitments[userCommitmentCount].digest = state.commitments[i].digest; output.commitments[userCommitmentCount].amount = state.commitments[i].amount; @@ -517,71 +575,73 @@ struct RANDOM : public ContractBase PUBLIC_FUNCTION(QueryPrice) { - const QueryPrice_input& inputData = input; output.price = state.pricePerByte - * inputData.numberOfBytes - * (inputData.minMinerDeposit / state.priceDepositDivisor +1); + * input.numberOfBytes + * (div(input.minMinerDeposit, (uint64)state.priceDepositDivisor) + 1); } - // -------------------------------------------------- - // Epoch End: Distribute pools - END_EPOCH() + END_EPOCH_WITH_LOCALS() { - // Process timeouts first - uint32 currentTick = qpi.tick(); - for (uint32 i = 0; i < state.commitmentCount; ) { - if (!state.commitments[i].hasRevealed && - currentTick > state.commitments[i].revealDeadlineTick) + locals.currentTick = qpi.tick(); + + for (locals.i = 0; locals.i < state.commitmentCount; ) { + if (!state.commitments[locals.i].hasRevealed && + locals.currentTick > state.commitments[locals.i].revealDeadlineTick) { - uint64 lostDeposit = state.commitments[i].amount; + uint64 lostDeposit = state.commitments[locals.i].amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; state.totalSecurityDepositsLocked -= lostDeposit; - if (i != state.commitmentCount - 1) - state.commitments[i] = state.commitments[state.commitmentCount - 1]; + if (locals.i != state.commitmentCount - 1) + { + state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + } state.commitmentCount--; } - else { - i++; + else + { + locals.i++; } } - // Distribute miner pool - if (state.minerEarningsPool > 0 && state.recentMinerCount > 0) { - uint64 payout = state.minerEarningsPool / state.recentMinerCount; - for (uint32 i = 0; i < state.recentMinerCount; ++i) { - if (!isZeroIdCheck(state.recentMiners[i].minerId)) - qpi.transfer(state.recentMiners[i].minerId, payout); + if (state.minerEarningsPool > 0 && state.recentMinerCount > 0) + { + locals.payout = div(state.minerEarningsPool, (uint64)state.recentMinerCount); + for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) + { + if (!isZeroIdCheck(state.recentMiners[locals.i].minerId)) + { + qpi.transfer(state.recentMiners[locals.i].minerId, locals.payout); + } } state.minerEarningsPool = 0; - for (uint32 i = 0; i < MAX_RECENT_MINERS; ++i) - state.recentMiners[i] = RecentMiner{}; + for (locals.i = 0; locals.i < MAX_RECENT_MINERS; ++locals.i) + { + state.recentMiners[locals.i] = RecentMiner{}; + } state.recentMinerCount = 0; } - // Distribute to shareholders - if (state.shareholderEarningsPool > 0) { - qpi.distributeDividends(state.shareholderEarningsPool / NUMBER_OF_COMPUTORS); + if (state.shareholderEarningsPool > 0) + { + qpi.distributeDividends(div(state.shareholderEarningsPool, (uint64)NUMBER_OF_COMPUTORS)); state.shareholderEarningsPool = 0; } - // Continue current lost deposit distribution as before if (state.pendingShareholderDistribution > 0) { - qpi.distributeDividends(state.pendingShareholderDistribution / NUMBER_OF_COMPUTORS); + qpi.distributeDividends(div(state.pendingShareholderDistribution, (uint64)NUMBER_OF_COMPUTORS)); state.pendingShareholderDistribution = 0; } } REGISTER_USER_FUNCTIONS_AND_PROCEDURES() { - // READ-ONLY USER FUNCTIONS REGISTER_USER_FUNCTION(GetContractInfo, 1); REGISTER_USER_FUNCTION(GetUserCommitments, 2); REGISTER_USER_FUNCTION(QueryPrice, 3); - // USER PROCEDURES REGISTER_USER_PROCEDURE(RevealAndCommit, 1); REGISTER_USER_PROCEDURE(BuyEntropy, 2); } @@ -589,34 +649,18 @@ struct RANDOM : public ContractBase INITIALIZE() { state.entropyHistoryHead = 0; - for (uint32 i = 0; i < ENTROPY_HISTORY_LEN; ++i) { - copyMemory(state.entropyHistory[i], m256i{}); - state.entropyPoolVersionHistory[i] = 0; - } - copyMemory(state.currentEntropyPool, m256i{}); - state.entropyPoolVersion = 0; - state.totalCommits = 0; - state.totalReveals = 0; - state.totalSecurityDepositsLocked = 0; - state.minimumSecurityDeposit = 1; // Now allow1 QU + state.minimumSecurityDeposit = 1; state.revealTimeoutTicks = 9; - state.commitmentCount = 0; - state.totalRevenue = 0; - state.pendingShareholderDistribution = 0; - state.lostDepositsRevenue = 0; - state.minerEarningsPool = 0; - state.shareholderEarningsPool = 0; - state.recentMinerCount = 0; state.pricePerByte = 10; state.priceDepositDivisor = 1000; - for (uint32 i = 0; i < 16; i++) { + + for (uint32 i = 0; i < 16; i++) + { state.validDepositAmounts[i] = 1ULL; for (uint32 j = 0; j < i; j++) + { state.validDepositAmounts[i] *= 10; + } } - for (uint32 i = 0; i < 1024; ++i) - state.commitments[i] = EntropyCommitment{}; - for (uint32 i = 0; i < MAX_RECENT_MINERS; ++i) - state.recentMiners[i] = RecentMiner{}; } -}; \ No newline at end of file +}; From 1f3ad9162e2e2e68fde1ad224d46749cf776858c Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Thu, 11 Dec 2025 10:05:17 +0100 Subject: [PATCH 03/12] Replace magic numbers with constants in Random.h --- src/contracts/Random.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index bbe8c2fc6..3d861d032 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -1,7 +1,7 @@ using namespace QPI; -// Prefer constexpr over #define constexpr uint32_t MAX_RECENT_MINERS = 369; +constexpr uint32_t MAX_COMMITMENTS = 1024; constexpr uint32_t ENTROPY_HISTORY_LEN = 3; // For 2-tick-back entropy pool struct RANDOM2 @@ -62,7 +62,7 @@ struct RANDOM : public ContractBase uint32 commitTick; uint32 revealDeadlineTick; bool hasRevealed; - } commitments[1024]; + } commitments[MAX_COMMITMENTS]; uint32 commitmentCount; // Helper functions (static inline) @@ -419,7 +419,7 @@ struct RANDOM : public ContractBase { if (isValidDepositAmountCheck(state, qpi.invocationReward()) && qpi.invocationReward() >= state.minimumSecurityDeposit) { - if (state.commitmentCount < 1024) + if (state.commitmentCount < MAX_COMMITMENTS) { state.commitments[state.commitmentCount].digest = input.committedDigest; state.commitments[state.commitmentCount].invocatorId = qpi.invocator(); From aa8a06d86c64ed1d0902a5d3933d500f3d7a517e Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Thu, 11 Dec 2025 12:41:03 +0100 Subject: [PATCH 04/12] Update Random.h --- src/contracts/Random.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index 3d861d032..165228a20 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -1,8 +1,8 @@ using namespace QPI; -constexpr uint32_t MAX_RECENT_MINERS = 369; -constexpr uint32_t MAX_COMMITMENTS = 1024; -constexpr uint32_t ENTROPY_HISTORY_LEN = 3; // For 2-tick-back entropy pool +constexpr uint32_t RANDOM_MAX_RECENT_MINERS = 369; +constexpr uint32_t RANDOM_MAX_COMMITMENTS = 1024; +constexpr uint32_t RANDOM_ENTROPY_HISTORY_LEN = 3; // For 2-tick-back entropy pool struct RANDOM2 { @@ -12,8 +12,8 @@ struct RANDOM : public ContractBase { private: // Entropy pool history (circular buffer for look-back) - m256i entropyHistory[ENTROPY_HISTORY_LEN]; - uint64 entropyPoolVersionHistory[ENTROPY_HISTORY_LEN]; + m256i entropyHistory[RANDOM_ENTROPY_HISTORY_LEN]; + uint64 entropyPoolVersionHistory[RANDOM_ENTROPY_HISTORY_LEN]; uint32 entropyHistoryHead; // points to most recent // Global entropy pool - combines all revealed entropy @@ -48,7 +48,7 @@ struct RANDOM : public ContractBase uint64 deposit; uint64 lastEntropyVersion; uint32 lastRevealTick; - } recentMiners[MAX_RECENT_MINERS]; + } recentMiners[RANDOM_MAX_RECENT_MINERS]; uint32 recentMinerCount; // Valid deposit amounts (powers of 10) @@ -62,7 +62,7 @@ struct RANDOM : public ContractBase uint32 commitTick; uint32 revealDeadlineTick; bool hasRevealed; - } commitments[MAX_COMMITMENTS]; + } commitments[RANDOM_MAX_COMMITMENTS]; uint32 commitmentCount; // Helper functions (static inline) @@ -76,7 +76,7 @@ struct RANDOM : public ContractBase stateRef.currentEntropyPool.m256i_u64[i] ^= entropyData[i]; } - stateRef.entropyHistoryHead = mod(stateRef.entropyHistoryHead + 1U, ENTROPY_HISTORY_LEN); + stateRef.entropyHistoryHead = mod(stateRef.entropyHistoryHead + 1U, RANDOM_ENTROPY_HISTORY_LEN); stateRef.entropyHistory[stateRef.entropyHistoryHead] = stateRef.currentEntropyPool; stateRef.entropyPoolVersion++; stateRef.entropyPoolVersionHistory[stateRef.entropyHistoryHead] = stateRef.entropyPoolVersion; @@ -84,7 +84,7 @@ struct RANDOM : public ContractBase static inline void generateRandomBytesData(const RANDOM& stateRef, uint8* output, uint32 numBytes, uint32 historyIdx, uint32 currentTick) { - const m256i selectedPool = stateRef.entropyHistory[mod(stateRef.entropyHistoryHead + ENTROPY_HISTORY_LEN - historyIdx, ENTROPY_HISTORY_LEN)]; + const m256i selectedPool = stateRef.entropyHistory[mod(stateRef.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - historyIdx, RANDOM_ENTROPY_HISTORY_LEN)]; m256i tickEntropy; tickEntropy.m256i_u64[0] = static_cast(currentTick); tickEntropy.m256i_u64[1] = 0; @@ -365,7 +365,7 @@ struct RANDOM : public ContractBase } else { - if (state.recentMinerCount < MAX_RECENT_MINERS) + if (state.recentMinerCount < RANDOM_MAX_RECENT_MINERS) { state.recentMiners[state.recentMinerCount].minerId = qpi.invocator(); state.recentMiners[state.recentMinerCount].deposit = state.commitments[locals.i].amount; @@ -377,7 +377,7 @@ struct RANDOM : public ContractBase { locals.lowestIx = 0; - for (locals.rm = 1; locals.rm < MAX_RECENT_MINERS; ++locals.rm) + for (locals.rm = 1; locals.rm < RANDOM_MAX_RECENT_MINERS; ++locals.rm) { if (state.recentMiners[locals.rm].deposit < state.recentMiners[locals.lowestIx].deposit || (state.recentMiners[locals.rm].deposit == state.recentMiners[locals.lowestIx].deposit && @@ -419,7 +419,7 @@ struct RANDOM : public ContractBase { if (isValidDepositAmountCheck(state, qpi.invocationReward()) && qpi.invocationReward() >= state.minimumSecurityDeposit) { - if (state.commitmentCount < MAX_COMMITMENTS) + if (state.commitmentCount < RANDOM_MAX_COMMITMENTS) { state.commitments[state.commitmentCount].digest = input.committedDigest; state.commitments[state.commitmentCount].invocatorId = qpi.invocator(); @@ -502,7 +502,7 @@ struct RANDOM : public ContractBase return; } - locals.histIdx = mod(state.entropyHistoryHead + ENTROPY_HISTORY_LEN - 2, ENTROPY_HISTORY_LEN); + locals.histIdx = mod(state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2, RANDOM_ENTROPY_HISTORY_LEN); generateRandomBytesData(state, output.randomBytes, (input.numberOfBytes > 32 ? 32 : input.numberOfBytes), locals.histIdx, locals.currentTick); output.entropyVersion = state.entropyPoolVersionHistory[locals.histIdx]; output.usedMinerDeposit = locals.usedMinerDeposit; @@ -616,7 +616,7 @@ struct RANDOM : public ContractBase } } state.minerEarningsPool = 0; - for (locals.i = 0; locals.i < MAX_RECENT_MINERS; ++locals.i) + for (locals.i = 0; locals.i < RANDOM_MAX_RECENT_MINERS; ++locals.i) { state.recentMiners[locals.i] = RecentMiner{}; } From cadca2f3a6561e4baede1a4244886a13a5a54fce Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Thu, 11 Dec 2025 19:55:23 +0100 Subject: [PATCH 05/12] Update Random.h --- src/contracts/Random.h | 441 +++++++++++++++++++++-------------------- 1 file changed, 229 insertions(+), 212 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index 165228a20..cddeb537f 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -1,23 +1,39 @@ using namespace QPI; -constexpr uint32_t RANDOM_MAX_RECENT_MINERS = 369; -constexpr uint32_t RANDOM_MAX_COMMITMENTS = 1024; -constexpr uint32_t RANDOM_ENTROPY_HISTORY_LEN = 3; // For 2-tick-back entropy pool +constexpr uint32_t RANDOM_MAX_RECENT_MINERS = 512; +constexpr uint32_t RANDOM_MAX_COMMITMENTS = 1024; +constexpr uint32_t RANDOM_ENTROPY_HISTORY_LEN = 4; -struct RANDOM2 +struct RANDOM2 {}; + +struct RecentMiner { + id minerId; + uint64 deposit; + uint64 lastEntropyVersion; + uint32 lastRevealTick; +}; + +struct EntropyCommitment +{ + id digest; + id invocatorId; + uint64 amount; + uint32 commitTick; + uint32 revealDeadlineTick; + bool hasRevealed; }; struct RANDOM : public ContractBase { private: - // Entropy pool history (circular buffer for look-back) - m256i entropyHistory[RANDOM_ENTROPY_HISTORY_LEN]; - uint64 entropyPoolVersionHistory[RANDOM_ENTROPY_HISTORY_LEN]; - uint32 entropyHistoryHead; // points to most recent + // Entropy pool history (circular buffer for look-back; N must be 2^N) + Array entropyHistory; + Array entropyPoolVersionHistory; + uint32 entropyHistoryHead; // points to most recent tick // Global entropy pool - combines all revealed entropy - m256i currentEntropyPool; + m256i currentEntropyPool; uint64 entropyPoolVersion; // Tracking statistics @@ -29,74 +45,61 @@ struct RANDOM : public ContractBase uint64 minimumSecurityDeposit; uint32 revealTimeoutTicks; // e.g. 9 ticks - // Revenue distribution system + // Revenue tracking uint64 totalRevenue; uint64 pendingShareholderDistribution; uint64 lostDepositsRevenue; - - // Earnings pools uint64 minerEarningsPool; uint64 shareholderEarningsPool; // Pricing config - uint64 pricePerByte; // e.g. 10 QU (default) + uint64 pricePerByte; // e.g. 10 QU (default) uint64 priceDepositDivisor; // e.g. 1000 (matches contract formula) - // Epoch tracking for miner rewards - struct RecentMiner { - id minerId; - uint64 deposit; - uint64 lastEntropyVersion; - uint32 lastRevealTick; - } recentMiners[RANDOM_MAX_RECENT_MINERS]; + // Miners (recent entropy providers) - LRU of high-value miners + Array recentMiners; uint32 recentMinerCount; // Valid deposit amounts (powers of 10) - uint64 validDepositAmounts[16]; + uint64 validDepositAmounts[16]; // Scalar QPI array allowed // Commitment tracking - struct EntropyCommitment { - id digest; - id invocatorId; - uint64 amount; - uint32 commitTick; - uint32 revealDeadlineTick; - bool hasRevealed; - } commitments[RANDOM_MAX_COMMITMENTS]; + Array commitments; uint32 commitmentCount; - // Helper functions (static inline) - // Remove local variables, pass as arguments if needed + // ----- Helpers ----- static inline void updateEntropyPoolData(RANDOM& stateRef, const bit_4096& newEntropy) { const uint64* entropyData = reinterpret_cast(&newEntropy); - for (uint32 i = 0; i < 4; i++) { stateRef.currentEntropyPool.m256i_u64[i] ^= entropyData[i]; } - stateRef.entropyHistoryHead = mod(stateRef.entropyHistoryHead + 1U, RANDOM_ENTROPY_HISTORY_LEN); - stateRef.entropyHistory[stateRef.entropyHistoryHead] = stateRef.currentEntropyPool; + stateRef.entropyHistoryHead = (stateRef.entropyHistoryHead + 1) % RANDOM_ENTROPY_HISTORY_LEN; + stateRef.entropyHistory.set(stateRef.entropyHistoryHead, stateRef.currentEntropyPool); + stateRef.entropyPoolVersion++; - stateRef.entropyPoolVersionHistory[stateRef.entropyHistoryHead] = stateRef.entropyPoolVersion; + stateRef.entropyPoolVersionHistory.set(stateRef.entropyHistoryHead, stateRef.entropyPoolVersion); } static inline void generateRandomBytesData(const RANDOM& stateRef, uint8* output, uint32 numBytes, uint32 historyIdx, uint32 currentTick) { - const m256i selectedPool = stateRef.entropyHistory[mod(stateRef.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - historyIdx, RANDOM_ENTROPY_HISTORY_LEN)]; + const m256i selectedPool = stateRef.entropyHistory.get( + (stateRef.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - historyIdx) % RANDOM_ENTROPY_HISTORY_LEN + ); + m256i tickEntropy; tickEntropy.m256i_u64[0] = static_cast(currentTick); tickEntropy.m256i_u64[1] = 0; tickEntropy.m256i_u64[2] = 0; tickEntropy.m256i_u64[3] = 0; - m256i combinedEntropy; + m256i combinedEntropy; for (uint32 i = 0; i < 4; i++) { combinedEntropy.m256i_u64[i] = selectedPool.m256i_u64[i] ^ tickEntropy.m256i_u64[i]; } - for (uint32 i = 0; i < ((numBytes > 32U) ? 32U : numBytes); i++) { output[i] = combinedEntropy.m256i_u8[i]; @@ -115,60 +118,61 @@ struct RANDOM : public ContractBase return false; } - static inline bool isEqualIdCheck(const id& a, const id& b) - { + static inline bool isEqualIdCheck(const id& a, const id& b) + { return a == b; } - static inline bool isZeroIdCheck(const id& value) - { - return isZero(value); + static inline bool isZeroIdCheck(const id& value) + { + return isZero(value); } static inline bool isZeroBitsCheck(const bit_4096& value) { const uint64* data = reinterpret_cast(&value); - for (uint32 i = 0; i < 64U; i++) { - if (data[i] != 0) + for (uint32 i = 0; i < 64U; i++) + { + if (data[i] != 0) { return false; } } - return true; } - static inline bool k12CommitmentMatches(const QPI::QpiContextFunctionCall& qpi, const QPI::bit_4096& revealedBits, const QPI::id& committedDigest) + static inline bool k12CommitmentMatches( + const QPI::QpiContextFunctionCall& qpi, + const QPI::bit_4096& revealedBits, + const QPI::id& committedDigest) { QPI::id computedDigest = qpi.K12(revealedBits); for (QPI::uint32 i = 0; i < 32U; i++) { - if (computedDigest.m256i_u8[i] != committedDigest.m256i_u8[i]) return false; + if (computedDigest.m256i_u8[i] != committedDigest.m256i_u8[i]) + { + return false; + } } return true; } public: - // -------------------------------------------------- - // Entropy mining (commit-reveal) + // ---------- API in/out types ---------- struct RevealAndCommit_input { - bit_4096 revealedBits; // Previous entropy to reveal (or zeros for first commit) - id committedDigest; // Hash of new entropy to commit (or zeros if stopping) + bit_4096 revealedBits; + id committedDigest; }; - struct RevealAndCommit_output { - uint8 randomBytes[32]; + uint8 randomBytes[32]; uint64 entropyVersion; - bool revealSuccessful; - bool commitSuccessful; + bool revealSuccessful; + bool commitSuccessful; uint64 depositReturned; }; - // -------------------------------------------------- - // READ-ONLY FUNCTIONS - struct GetContractInfo_input {}; struct GetContractInfo_output { @@ -181,7 +185,6 @@ struct RANDOM : public ContractBase uint64 validDepositAmounts[16]; uint32 currentTick; uint64 entropyPoolVersion; - // Revenue + pools uint64 totalRevenue; uint64 pendingShareholderDistribution; uint64 lostDepositsRevenue; @@ -190,10 +193,14 @@ struct RANDOM : public ContractBase uint32 recentMinerCount; }; - struct GetUserCommitments_input { id userId; }; + struct GetUserCommitments_input + { + id userId; + }; struct GetUserCommitments_output { - struct UserCommitment { + struct UserCommitment + { id digest; uint64 amount; uint32 commitTick; @@ -203,42 +210,32 @@ struct RANDOM : public ContractBase uint32 commitmentCount; }; - // -------------------------------------------------- - // SELL ENTROPY (random bytes) struct BuyEntropy_input { - uint32 numberOfBytes; // 1-32 - uint64 minMinerDeposit; // required deposit of recent miner + uint32 numberOfBytes; + uint64 minMinerDeposit; }; struct BuyEntropy_output { - bool success; - uint8 randomBytes[32]; - uint64 entropyVersion; // version of pool 2 ticks ago! + bool success; + uint8 randomBytes[32]; + uint64 entropyVersion; uint64 usedMinerDeposit; uint64 usedPoolVersion; }; - // -------------------------------------------------- - // CLAIMING struct ClaimMinerEarnings_input {}; - struct ClaimMinerEarnings_output - { - uint64 payout; - }; + struct ClaimMinerEarnings_output { uint64 payout; }; - // -------------------------------------------------- - // PRICE QUERY struct QueryPrice_input { uint32 numberOfBytes; uint64 minMinerDeposit; }; - struct QueryPrice_output { - uint64 price; - }; + struct QueryPrice_output { uint64 price; }; - // Locals structs for WITH_LOCALS macros - struct RevealAndCommit_locals { + // --- Locals for macro-produced procedures --- + struct RevealAndCommit_locals + { uint32 currentTick; bool hasRevealData; bool hasNewCommit; @@ -249,7 +246,8 @@ struct RANDOM : public ContractBase uint32 lowestIx; bool hashMatches; }; - struct BuyEntropy_locals { + struct BuyEntropy_locals + { uint32 currentTick; bool eligible; uint64 usedMinerDeposit; @@ -259,52 +257,58 @@ struct RANDOM : public ContractBase uint32 histIdx; uint64 half; }; - struct END_EPOCH_locals { + struct END_EPOCH_locals + { uint32 currentTick; uint32 i; uint64 payout; }; // -------------------------------------------------- - // Mining: RevealAndCommit PUBLIC_PROCEDURE_WITH_LOCALS(RevealAndCommit) { locals.currentTick = qpi.tick(); - for (locals.i = 0; locals.i < state.commitmentCount; ) + // Remove expired commitments + for (locals.i = 0; locals.i < state.commitmentCount;) { - if (!state.commitments[locals.i].hasRevealed && - locals.currentTick > state.commitments[locals.i].revealDeadlineTick) + EntropyCommitment cmt = state.commitments.get(locals.i); // value copy + if (!cmt.hasRevealed && locals.currentTick > cmt.revealDeadlineTick) { - uint64 lostDeposit = state.commitments[locals.i].amount; + uint64 lostDeposit = cmt.amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; state.totalSecurityDepositsLocked -= lostDeposit; if (locals.i != state.commitmentCount - 1) - state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + { + state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + } state.commitmentCount--; } - else + else { locals.i++; } } - if (qpi.numberOfTickTransactions() == -1) + // Unclaimed deposits: return forcibly at end of tick + if (qpi.numberOfTickTransactions() == -1) { - for (locals.i = 0; locals.i < state.commitmentCount; ) + for (locals.i = 0; locals.i < state.commitmentCount;) { - if (!state.commitments[locals.i].hasRevealed && - state.commitments[locals.i].revealDeadlineTick == qpi.tick()) + EntropyCommitment cmt = state.commitments.get(locals.i); + if (!cmt.hasRevealed && cmt.revealDeadlineTick == qpi.tick()) { - qpi.transfer(state.commitments[locals.i].invocatorId, state.commitments[locals.i].amount); - state.totalSecurityDepositsLocked -= state.commitments[locals.i].amount; + qpi.transfer(cmt.invocatorId, cmt.amount); + state.totalSecurityDepositsLocked -= cmt.amount; if (locals.i != state.commitmentCount - 1) - state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + { + state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + } state.commitmentCount--; } - else + else { locals.i++; } @@ -316,20 +320,21 @@ struct RANDOM : public ContractBase locals.hasNewCommit = !isZeroIdCheck(input.committedDigest); locals.isStoppingMining = (qpi.invocationReward() == 0); + // Reveal logic (return deposit, add entropy, update recent miner stats) if (locals.hasRevealData) { - for (locals.i = 0; locals.i < state.commitmentCount; ) + for (locals.i = 0; locals.i < state.commitmentCount;) { - if (!state.commitments[locals.i].hasRevealed && - isEqualIdCheck(state.commitments[locals.i].invocatorId, qpi.invocator())) + EntropyCommitment cmt = state.commitments.get(locals.i); + if (!cmt.hasRevealed && isEqualIdCheck(cmt.invocatorId, qpi.invocator())) { - locals.hashMatches = k12CommitmentMatches(qpi, input.revealedBits, state.commitments[locals.i].digest); + locals.hashMatches = k12CommitmentMatches(qpi, input.revealedBits, cmt.digest); if (locals.hashMatches) { - if (locals.currentTick > state.commitments[locals.i].revealDeadlineTick) + if (locals.currentTick > cmt.revealDeadlineTick) { - uint64 lostDeposit = state.commitments[locals.i].amount; + uint64 lostDeposit = cmt.amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; @@ -338,76 +343,79 @@ struct RANDOM : public ContractBase else { updateEntropyPoolData(state, input.revealedBits); - qpi.transfer(qpi.invocator(), state.commitments[locals.i].amount); + qpi.transfer(qpi.invocator(), cmt.amount); output.revealSuccessful = true; - output.depositReturned = state.commitments[locals.i].amount; + output.depositReturned = cmt.amount; state.totalReveals++; - state.totalSecurityDepositsLocked -= state.commitments[locals.i].amount; - locals.existingIndex = -1; + state.totalSecurityDepositsLocked -= cmt.amount; - for (locals.rm = 0; locals.rm < state.recentMinerCount; ++locals.rm) + // Maintain LRU recentMiner list for rewards + locals.existingIndex = -1; + for (locals.rm = 0; locals.rm < state.recentMinerCount; ++locals.rm) { - if (isEqualIdCheck(state.recentMiners[locals.rm].minerId, qpi.invocator())) + RecentMiner rm = state.recentMiners.get(locals.rm); + if (isEqualIdCheck(rm.minerId, qpi.invocator())) { locals.existingIndex = locals.rm; break; } } - - if (locals.existingIndex >= 0) + if (locals.existingIndex >= 0) { - if (state.recentMiners[locals.existingIndex].deposit < state.commitments[locals.i].amount) + RecentMiner rm = state.recentMiners.get(locals.existingIndex); + if (rm.deposit < cmt.amount) { - state.recentMiners[locals.existingIndex].deposit = state.commitments[locals.i].amount; - state.recentMiners[locals.existingIndex].lastEntropyVersion = state.entropyPoolVersion; + rm.deposit = cmt.amount; + rm.lastEntropyVersion = state.entropyPoolVersion; } - state.recentMiners[locals.existingIndex].lastRevealTick = locals.currentTick; + rm.lastRevealTick = locals.currentTick; + state.recentMiners.set(locals.existingIndex, rm); } - else + else if (state.recentMinerCount < RANDOM_MAX_RECENT_MINERS) { - if (state.recentMinerCount < RANDOM_MAX_RECENT_MINERS) - { - state.recentMiners[state.recentMinerCount].minerId = qpi.invocator(); - state.recentMiners[state.recentMinerCount].deposit = state.commitments[locals.i].amount; - state.recentMiners[state.recentMinerCount].lastEntropyVersion = state.entropyPoolVersion; - state.recentMiners[state.recentMinerCount].lastRevealTick = locals.currentTick; - state.recentMinerCount++; - } - else + RecentMiner rm; + rm.minerId = qpi.invocator(); + rm.deposit = cmt.amount; + rm.lastEntropyVersion = state.entropyPoolVersion; + rm.lastRevealTick = locals.currentTick; + state.recentMiners.set(state.recentMinerCount, rm); + state.recentMinerCount++; + } + else + { + locals.lowestIx = 0; + for (locals.rm = 1; locals.rm < RANDOM_MAX_RECENT_MINERS; ++locals.rm) { - locals.lowestIx = 0; - - for (locals.rm = 1; locals.rm < RANDOM_MAX_RECENT_MINERS; ++locals.rm) + RecentMiner test = state.recentMiners.get(locals.rm); + RecentMiner lo = state.recentMiners.get(locals.lowestIx); + if (test.deposit < lo.deposit || + (test.deposit == lo.deposit && test.lastEntropyVersion < lo.lastEntropyVersion)) { - if (state.recentMiners[locals.rm].deposit < state.recentMiners[locals.lowestIx].deposit || - (state.recentMiners[locals.rm].deposit == state.recentMiners[locals.lowestIx].deposit && - state.recentMiners[locals.rm].lastEntropyVersion < state.recentMiners[locals.lowestIx].lastEntropyVersion)) - { - locals.lowestIx = locals.rm; - } - } - - if (state.commitments[locals.i].amount > state.recentMiners[locals.lowestIx].deposit || - (state.commitments[locals.i].amount == state.recentMiners[locals.lowestIx].deposit && - state.entropyPoolVersion > state.recentMiners[locals.lowestIx].lastEntropyVersion)) - { - state.recentMiners[locals.lowestIx].minerId = qpi.invocator(); - state.recentMiners[locals.lowestIx].deposit = state.commitments[locals.i].amount; - state.recentMiners[locals.lowestIx].lastEntropyVersion = state.entropyPoolVersion; - state.recentMiners[locals.lowestIx].lastRevealTick = locals.currentTick; + locals.lowestIx = locals.rm; } } + RecentMiner rm = state.recentMiners.get(locals.lowestIx); + if ( + cmt.amount > rm.deposit || + (cmt.amount == rm.deposit && state.entropyPoolVersion > rm.lastEntropyVersion) + ) + { + rm.minerId = qpi.invocator(); + rm.deposit = cmt.amount; + rm.lastEntropyVersion = state.entropyPoolVersion; + rm.lastRevealTick = locals.currentTick; + state.recentMiners.set(locals.lowestIx, rm); + } } } - state.totalSecurityDepositsLocked -= state.commitments[locals.i].amount; + state.totalSecurityDepositsLocked -= cmt.amount; if (locals.i != state.commitmentCount - 1) { - state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); } state.commitmentCount--; - continue; } } @@ -415,18 +423,24 @@ struct RANDOM : public ContractBase } } + // New commitment/registration for reward round if (locals.hasNewCommit && !locals.isStoppingMining) { - if (isValidDepositAmountCheck(state, qpi.invocationReward()) && qpi.invocationReward() >= state.minimumSecurityDeposit) + if ( + isValidDepositAmountCheck(state, qpi.invocationReward()) && + qpi.invocationReward() >= state.minimumSecurityDeposit + ) { if (state.commitmentCount < RANDOM_MAX_COMMITMENTS) { - state.commitments[state.commitmentCount].digest = input.committedDigest; - state.commitments[state.commitmentCount].invocatorId = qpi.invocator(); - state.commitments[state.commitmentCount].amount = qpi.invocationReward(); - state.commitments[state.commitmentCount].commitTick = locals.currentTick; - state.commitments[state.commitmentCount].revealDeadlineTick = locals.currentTick + state.revealTimeoutTicks; - state.commitments[state.commitmentCount].hasRevealed = false; + EntropyCommitment ncmt; + ncmt.digest = input.committedDigest; + ncmt.invocatorId = qpi.invocator(); + ncmt.amount = qpi.invocationReward(); + ncmt.commitTick = locals.currentTick; + ncmt.revealDeadlineTick = locals.currentTick + state.revealTimeoutTicks; + ncmt.hasRevealed = false; + state.commitments.set(state.commitmentCount, ncmt); state.commitmentCount++; state.totalCommits++; state.totalSecurityDepositsLocked += qpi.invocationReward(); @@ -439,34 +453,34 @@ struct RANDOM : public ContractBase output.entropyVersion = state.entropyPoolVersion; } - // BUY ENTROPY / RANDOM BYTES PUBLIC_PROCEDURE_WITH_LOCALS(BuyEntropy) { locals.currentTick = qpi.tick(); - for (locals.i = 0; locals.i < state.commitmentCount; ) + // Housekeeping: remove expired commitments + for (locals.i = 0; locals.i < state.commitmentCount;) { - if (!state.commitments[locals.i].hasRevealed && - locals.currentTick > state.commitments[locals.i].revealDeadlineTick) + EntropyCommitment cmt = state.commitments.get(locals.i); + if (!cmt.hasRevealed && locals.currentTick > cmt.revealDeadlineTick) { - uint64 lostDeposit = state.commitments[locals.i].amount; + uint64 lostDeposit = cmt.amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; state.totalSecurityDepositsLocked -= lostDeposit; if (locals.i != state.commitmentCount - 1) { - state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); } state.commitmentCount--; } - else + else { locals.i++; } } - if (qpi.numberOfTickTransactions() == -1) + if (qpi.numberOfTickTransactions() == -1) { output.success = false; return; @@ -477,13 +491,17 @@ struct RANDOM : public ContractBase locals.eligible = false; locals.usedMinerDeposit = 0; - for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) + // Find eligible recent miner + for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) { - if (state.recentMiners[locals.i].deposit >= input.minMinerDeposit && - (locals.currentTick - state.recentMiners[locals.i].lastRevealTick) <= state.revealTimeoutTicks) + RecentMiner rm = state.recentMiners.get(locals.i); + if ( + rm.deposit >= input.minMinerDeposit && + (locals.currentTick - rm.lastRevealTick) <= state.revealTimeoutTicks + ) { locals.eligible = true; - locals.usedMinerDeposit = state.recentMiners[locals.i].deposit; + locals.usedMinerDeposit = rm.deposit; break; } } @@ -493,20 +511,26 @@ struct RANDOM : public ContractBase return; } - locals.minPrice = state.pricePerByte - * input.numberOfBytes - * (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); + locals.minPrice = state.pricePerByte * input.numberOfBytes * + (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); if (locals.buyerFee < locals.minPrice) { return; } - locals.histIdx = mod(state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2, RANDOM_ENTROPY_HISTORY_LEN); - generateRandomBytesData(state, output.randomBytes, (input.numberOfBytes > 32 ? 32 : input.numberOfBytes), locals.histIdx, locals.currentTick); - output.entropyVersion = state.entropyPoolVersionHistory[locals.histIdx]; + locals.histIdx = (state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2) % RANDOM_ENTROPY_HISTORY_LEN; + generateRandomBytesData( + state, + output.randomBytes, + (input.numberOfBytes > 32 ? 32 : input.numberOfBytes), + locals.histIdx, + locals.currentTick + ); + + output.entropyVersion = state.entropyPoolVersionHistory.get(locals.histIdx); output.usedMinerDeposit = locals.usedMinerDeposit; - output.usedPoolVersion = state.entropyPoolVersionHistory[locals.histIdx]; + output.usedPoolVersion = state.entropyPoolVersionHistory.get(locals.histIdx); output.success = true; locals.half = div(locals.buyerFee, (uint64)2); @@ -514,13 +538,10 @@ struct RANDOM : public ContractBase state.shareholderEarningsPool += (locals.buyerFee - locals.half); } - // -------------------------------------------------- - // Read-only contract info PUBLIC_FUNCTION(GetContractInfo) { uint32 currentTick = qpi.tick(); uint32 activeCount = 0; - uint32 i; output.totalCommits = state.totalCommits; output.totalReveals = state.totalReveals; @@ -533,19 +554,17 @@ struct RANDOM : public ContractBase output.totalRevenue = state.totalRevenue; output.pendingShareholderDistribution = state.pendingShareholderDistribution; output.lostDepositsRevenue = state.lostDepositsRevenue; - output.minerEarningsPool = state.minerEarningsPool; output.shareholderEarningsPool = state.shareholderEarningsPool; output.recentMinerCount = state.recentMinerCount; - for (i = 0; i < 16; i++) + for (uint32 i = 0; i < 16; ++i) { output.validDepositAmounts[i] = state.validDepositAmounts[i]; } - - for (i = 0; i < state.commitmentCount; i++) + for (uint32 i = 0; i < state.commitmentCount; ++i) { - if (!state.commitments[i].hasRevealed) + if (!state.commitments.get(i).hasRevealed) { activeCount++; } @@ -556,17 +575,16 @@ struct RANDOM : public ContractBase PUBLIC_FUNCTION(GetUserCommitments) { uint32 userCommitmentCount = 0; - uint32 i; - - for (i = 0; i < state.commitmentCount && userCommitmentCount < 32; i++) + for (uint32 i = 0; i < state.commitmentCount && userCommitmentCount < 32; i++) { - if (isEqualIdCheck(state.commitments[i].invocatorId, input.userId)) + EntropyCommitment cmt = state.commitments.get(i); + if (isEqualIdCheck(cmt.invocatorId, input.userId)) { - output.commitments[userCommitmentCount].digest = state.commitments[i].digest; - output.commitments[userCommitmentCount].amount = state.commitments[i].amount; - output.commitments[userCommitmentCount].commitTick = state.commitments[i].commitTick; - output.commitments[userCommitmentCount].revealDeadlineTick = state.commitments[i].revealDeadlineTick; - output.commitments[userCommitmentCount].hasRevealed = state.commitments[i].hasRevealed; + output.commitments[userCommitmentCount].digest = cmt.digest; + output.commitments[userCommitmentCount].amount = cmt.amount; + output.commitments[userCommitmentCount].commitTick = cmt.commitTick; + output.commitments[userCommitmentCount].revealDeadlineTick = cmt.revealDeadlineTick; + output.commitments[userCommitmentCount].hasRevealed = cmt.hasRevealed; userCommitmentCount++; } } @@ -575,60 +593,59 @@ struct RANDOM : public ContractBase PUBLIC_FUNCTION(QueryPrice) { - output.price = state.pricePerByte - * input.numberOfBytes - * (div(input.minMinerDeposit, (uint64)state.priceDepositDivisor) + 1); + output.price = state.pricePerByte * input.numberOfBytes * + (div(input.minMinerDeposit, (uint64)state.priceDepositDivisor) + 1); } END_EPOCH_WITH_LOCALS() { locals.currentTick = qpi.tick(); - - for (locals.i = 0; locals.i < state.commitmentCount; ) { - if (!state.commitments[locals.i].hasRevealed && - locals.currentTick > state.commitments[locals.i].revealDeadlineTick) + for (locals.i = 0; locals.i < state.commitmentCount;) + { + EntropyCommitment cmt = state.commitments.get(locals.i); + if (!cmt.hasRevealed && locals.currentTick > cmt.revealDeadlineTick) { - uint64 lostDeposit = state.commitments[locals.i].amount; + uint64 lostDeposit = cmt.amount; state.lostDepositsRevenue += lostDeposit; state.totalRevenue += lostDeposit; state.pendingShareholderDistribution += lostDeposit; state.totalSecurityDepositsLocked -= lostDeposit; + if (locals.i != state.commitmentCount - 1) { - state.commitments[locals.i] = state.commitments[state.commitmentCount - 1]; + state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); } state.commitmentCount--; } - else + else { locals.i++; } } - if (state.minerEarningsPool > 0 && state.recentMinerCount > 0) + if (state.minerEarningsPool > 0 && state.recentMinerCount > 0) { locals.payout = div(state.minerEarningsPool, (uint64)state.recentMinerCount); - for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) + for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) { - if (!isZeroIdCheck(state.recentMiners[locals.i].minerId)) + RecentMiner rm = state.recentMiners.get(locals.i); + if (!isZeroIdCheck(rm.minerId)) { - qpi.transfer(state.recentMiners[locals.i].minerId, locals.payout); + qpi.transfer(rm.minerId, locals.payout); } } state.minerEarningsPool = 0; for (locals.i = 0; locals.i < RANDOM_MAX_RECENT_MINERS; ++locals.i) { - state.recentMiners[locals.i] = RecentMiner{}; + state.recentMiners.set(locals.i, RecentMiner{}); } state.recentMinerCount = 0; } - - if (state.shareholderEarningsPool > 0) + if (state.shareholderEarningsPool > 0) { qpi.distributeDividends(div(state.shareholderEarningsPool, (uint64)NUMBER_OF_COMPUTORS)); state.shareholderEarningsPool = 0; } - if (state.pendingShareholderDistribution > 0) { qpi.distributeDividends(div(state.pendingShareholderDistribution, (uint64)NUMBER_OF_COMPUTORS)); @@ -654,10 +671,10 @@ struct RANDOM : public ContractBase state.pricePerByte = 10; state.priceDepositDivisor = 1000; - for (uint32 i = 0; i < 16; i++) + for (uint32 i = 0; i < 16; ++i) { state.validDepositAmounts[i] = 1ULL; - for (uint32 j = 0; j < i; j++) + for (uint32 j = 0; j < i; ++j) { state.validDepositAmounts[i] *= 10; } From 17f7c0341a4ba1a2f2113efc7f10eab2890d0f38 Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Thu, 11 Dec 2025 19:56:13 +0100 Subject: [PATCH 06/12] Update contract_random.cpp --- test/contract_random.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/contract_random.cpp b/test/contract_random.cpp index c692b513d..62eee4c2d 100644 --- a/test/contract_random.cpp +++ b/test/contract_random.cpp @@ -288,11 +288,11 @@ TEST(ContractRandom, EndEpochDistribution) TEST(ContractRandom, RecentMinerEvictionPolicy) { ContractTestingRandom random; - const int maxMiners = MAX_RECENT_MINERS; + const int maxMiners = RANDOM_MAX_RECENT_MINERS; std::vector miners; auto baseDeposit = 1000; - // Fill up to MAX_RECENT_MINERS, all with same deposit + // Fill up to RANDOM_MAX_RECENT_MINERS, all with same deposit for (int i = 0; i < maxMiners; ++i) { auto miner = ContractTestingRandom::testId(5000 + i); miners.push_back(miner); @@ -508,4 +508,5 @@ TEST(ContractRandom, OutOfOrderRevealAndCompaction) RANDOM::GetContractInfo_output co{}; random.callFunction(0, 1, ci, co); EXPECT_EQ(co.activeCommitments, 0); -} \ No newline at end of file + +} From 9b372a72eee8f97f8b069c8ee50d58e08e744b93 Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Sat, 13 Dec 2025 13:15:59 +0100 Subject: [PATCH 07/12] Update contract_random.cpp --- test/contract_random.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/contract_random.cpp b/test/contract_random.cpp index 62eee4c2d..a48b0affa 100644 --- a/test/contract_random.cpp +++ b/test/contract_random.cpp @@ -1,4 +1,4 @@ -#define NO_UEFI +#define NO_UEFI #include #include "contract_testing.h" @@ -508,5 +508,4 @@ TEST(ContractRandom, OutOfOrderRevealAndCompaction) RANDOM::GetContractInfo_output co{}; random.callFunction(0, 1, ci, co); EXPECT_EQ(co.activeCommitments, 0); - } From e970e973797750b19f72443aed3b872911e7067e Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Sat, 13 Dec 2025 13:16:35 +0100 Subject: [PATCH 08/12] Update contract_random.cpp From 20087b6144937da50335acd0829eb324124e4f41 Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Sat, 13 Dec 2025 13:17:10 +0100 Subject: [PATCH 09/12] Refactor Random contract for improved clarity Refactor Random contract structure and logic, improving clarity and organization. Update data structures and variable names for consistency. --- src/contracts/Random.h | 601 ++++++++++++++++++++++++----------------- 1 file changed, 351 insertions(+), 250 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index cddeb537f..98e804818 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -1,12 +1,20 @@ using namespace QPI; -constexpr uint32_t RANDOM_MAX_RECENT_MINERS = 512; -constexpr uint32_t RANDOM_MAX_COMMITMENTS = 1024; -constexpr uint32_t RANDOM_ENTROPY_HISTORY_LEN = 4; +// Random contract: collects entropy reveals (commit-reveal), maintains an entropy pool, +// lets buyers purchase bytes of entropy, and pays miners/shareholders. + +// Key sizes and limits: +constexpr uint32_t RANDOM_MAX_RECENT_MINERS = 512; // 2^9 +constexpr uint32_t RANDOM_MAX_COMMITMENTS = 1024; // 2^10 +constexpr uint32_t RANDOM_ENTROPY_HISTORY_LEN = 4; // 2^2, even if 3 would suffice +constexpr uint32_t RANDOM_VALID_DEPOSIT_AMOUNTS = 16; +constexpr uint32_t RANDOM_MAX_USER_COMMITMENTS = 32; +constexpr uint32_t RANDOM_RANDOMBYTES_LEN = 32; struct RANDOM2 {}; -struct RecentMiner +// Recent miner info (LRU-ish tracking used to reward miners) +struct RANDOM_RecentMiner { id minerId; uint64 deposit; @@ -14,108 +22,70 @@ struct RecentMiner uint32 lastRevealTick; }; -struct EntropyCommitment +// Stored commitment created by miners (commit-reveal scheme) +struct RANDOM_EntropyCommitment { - id digest; - id invocatorId; - uint64 amount; + id digest; // K12(revealedBits) stored at commit time + id invocatorId; // who committed + uint64 amount; // security deposit uint32 commitTick; uint32 revealDeadlineTick; bool hasRevealed; }; +// Contract state and logic struct RANDOM : public ContractBase { private: - // Entropy pool history (circular buffer for look-back; N must be 2^N) + // --- QPI contract state --- + + // Circular history of recent entropy pools (m256i) Array entropyHistory; Array entropyPoolVersionHistory; - uint32 entropyHistoryHead; // points to most recent tick + uint32 entropyHistoryHead; - // Global entropy pool - combines all revealed entropy - m256i currentEntropyPool; + // current 256-bit entropy pool and its version + m256i currentEntropyPool; uint64 entropyPoolVersion; - // Tracking statistics + // Metrics and bookkeeping uint64 totalCommits; uint64 totalReveals; uint64 totalSecurityDepositsLocked; - // Contract configuration + // Configurable parameters uint64 minimumSecurityDeposit; - uint32 revealTimeoutTicks; // e.g. 9 ticks + uint32 revealTimeoutTicks; - // Revenue tracking + // Revenue accounting uint64 totalRevenue; uint64 pendingShareholderDistribution; uint64 lostDepositsRevenue; uint64 minerEarningsPool; uint64 shareholderEarningsPool; - // Pricing config - uint64 pricePerByte; // e.g. 10 QU (default) - uint64 priceDepositDivisor; // e.g. 1000 (matches contract formula) + // Pricing + uint64 pricePerByte; + uint64 priceDepositDivisor; - // Miners (recent entropy providers) - LRU of high-value miners - Array recentMiners; + // Recent miners (LRU-like), used to split miner earnings + Array recentMiners; uint32 recentMinerCount; - // Valid deposit amounts (powers of 10) - uint64 validDepositAmounts[16]; // Scalar QPI array allowed + // Allowed deposit amounts (valid security deposits) + Array validDepositAmounts; - // Commitment tracking - Array commitments; + // Active commitments (commitments array + count) + Array commitments; uint32 commitmentCount; - // ----- Helpers ----- - static inline void updateEntropyPoolData(RANDOM& stateRef, const bit_4096& newEntropy) - { - const uint64* entropyData = reinterpret_cast(&newEntropy); - for (uint32 i = 0; i < 4; i++) - { - stateRef.currentEntropyPool.m256i_u64[i] ^= entropyData[i]; - } - - stateRef.entropyHistoryHead = (stateRef.entropyHistoryHead + 1) % RANDOM_ENTROPY_HISTORY_LEN; - stateRef.entropyHistory.set(stateRef.entropyHistoryHead, stateRef.currentEntropyPool); - - stateRef.entropyPoolVersion++; - stateRef.entropyPoolVersionHistory.set(stateRef.entropyHistoryHead, stateRef.entropyPoolVersion); - } - - static inline void generateRandomBytesData(const RANDOM& stateRef, uint8* output, uint32 numBytes, uint32 historyIdx, uint32 currentTick) - { - const m256i selectedPool = stateRef.entropyHistory.get( - (stateRef.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - historyIdx) % RANDOM_ENTROPY_HISTORY_LEN - ); - - m256i tickEntropy; - tickEntropy.m256i_u64[0] = static_cast(currentTick); - tickEntropy.m256i_u64[1] = 0; - tickEntropy.m256i_u64[2] = 0; - tickEntropy.m256i_u64[3] = 0; - - m256i combinedEntropy; - for (uint32 i = 0; i < 4; i++) - { - combinedEntropy.m256i_u64[i] = selectedPool.m256i_u64[i] ^ tickEntropy.m256i_u64[i]; - } - for (uint32 i = 0; i < ((numBytes > 32U) ? 32U : numBytes); i++) - { - output[i] = combinedEntropy.m256i_u8[i]; - } - } + // --- QPI-compliant helpers --- + + // Simple helpers that avoid forbidden constructs in contracts. - static inline bool isValidDepositAmountCheck(const RANDOM& stateRef, uint64 amount) + static inline bool validDepositAmountAt(const RANDOM& stateRef, uint64 amount, uint32 idx) { - for (uint32 i = 0; i < 16U; i++) - { - if (amount == stateRef.validDepositAmounts[i]) - { - return true; - } - } - return false; + return amount == stateRef.validDepositAmounts.get(idx); } static inline bool isEqualIdCheck(const id& a, const id& b) @@ -128,37 +98,9 @@ struct RANDOM : public ContractBase return isZero(value); } - static inline bool isZeroBitsCheck(const bit_4096& value) - { - const uint64* data = reinterpret_cast(&value); - for (uint32 i = 0; i < 64U; i++) - { - if (data[i] != 0) - { - return false; - } - } - return true; - } - - static inline bool k12CommitmentMatches( - const QPI::QpiContextFunctionCall& qpi, - const QPI::bit_4096& revealedBits, - const QPI::id& committedDigest) - { - QPI::id computedDigest = qpi.K12(revealedBits); - for (QPI::uint32 i = 0; i < 32U; i++) - { - if (computedDigest.m256i_u8[i] != committedDigest.m256i_u8[i]) - { - return false; - } - } - return true; - } - public: - // ---------- API in/out types ---------- + // --- Inputs / outputs for user-facing procedures and functions --- + struct RevealAndCommit_input { bit_4096 revealedBits; @@ -166,7 +108,7 @@ struct RANDOM : public ContractBase }; struct RevealAndCommit_output { - uint8 randomBytes[32]; + Array randomBytes; uint64 entropyVersion; bool revealSuccessful; bool commitSuccessful; @@ -182,7 +124,7 @@ struct RANDOM : public ContractBase uint64 minimumSecurityDeposit; uint32 revealTimeoutTicks; uint32 activeCommitments; - uint64 validDepositAmounts[16]; + Array validDepositAmounts; uint32 currentTick; uint64 entropyPoolVersion; uint64 totalRevenue; @@ -206,7 +148,8 @@ struct RANDOM : public ContractBase uint32 commitTick; uint32 revealDeadlineTick; bool hasRevealed; - } commitments[32]; + }; + Array commitments; uint32 commitmentCount; }; @@ -218,22 +161,17 @@ struct RANDOM : public ContractBase struct BuyEntropy_output { bool success; - uint8 randomBytes[32]; + Array randomBytes; uint64 entropyVersion; uint64 usedMinerDeposit; uint64 usedPoolVersion; }; - struct ClaimMinerEarnings_input {}; - struct ClaimMinerEarnings_output { uint64 payout; }; - - struct QueryPrice_input { - uint32 numberOfBytes; - uint64 minMinerDeposit; - }; + struct QueryPrice_input { uint32 numberOfBytes; uint64 minMinerDeposit; }; struct QueryPrice_output { uint64 price; }; - // --- Locals for macro-produced procedures --- + //---- Locals storage for procedures --- + struct RevealAndCommit_locals { uint32 currentTick; @@ -245,6 +183,27 @@ struct RANDOM : public ContractBase uint32 rm; uint32 lowestIx; bool hashMatches; + + // locals for random-bytes generation (no stack locals) + uint32 histIdx; + uint32 rb_i; + + // precompute K12 digest of revealedBits once per call + id revealedDigest; + + // per-iteration commitment + RANDOM_EntropyCommitment cmt; + + // per-iteration temporaries (moved into locals for compliance) + uint64 lostDeposit; + RANDOM_RecentMiner recentMinerA; + RANDOM_RecentMiner recentMinerB; + + // deposit validity flag moved into locals + bool depositValid; + + // temporary used to create new commitments (moved out of stack) + RANDOM_EntropyCommitment ncmt; }; struct BuyEntropy_locals { @@ -256,33 +215,72 @@ struct RANDOM : public ContractBase uint64 buyerFee; uint32 histIdx; uint64 half; + + RANDOM_EntropyCommitment cmt; + + // per-iteration temporaries + uint64 lostDeposit; + RANDOM_RecentMiner recentMinerTemp; }; struct END_EPOCH_locals { uint32 currentTick; uint32 i; uint64 payout; + + RANDOM_EntropyCommitment cmt; + + // per-iteration temporaries + uint64 lostDeposit; + RANDOM_RecentMiner recentMinerTemp; + }; + struct GetUserCommitments_locals + { + uint32 userCommitmentCount; + uint32 i; + + RANDOM_EntropyCommitment cmt; + GetUserCommitments_output::UserCommitment ucmt; + }; + struct GetContractInfo_locals + { + uint32 currentTick; + uint32 activeCount; + uint32 i; + }; + struct INITIALIZE_locals + { + uint32 i; + uint32 j; + uint64 val; }; // -------------------------------------------------- + // RevealAndCommit procedure: + // - Removes expired commitments + // - Optionally processes a reveal (preimage) and returns deposit if valid + // - Optionally accepts a new commitment (invocation reward as deposit) + PUBLIC_PROCEDURE_WITH_LOCALS(RevealAndCommit) { locals.currentTick = qpi.tick(); - // Remove expired commitments + // Remove expired commitments (sweep) -- reclaim lost deposits into revenue pools for (locals.i = 0; locals.i < state.commitmentCount;) { - EntropyCommitment cmt = state.commitments.get(locals.i); // value copy - if (!cmt.hasRevealed && locals.currentTick > cmt.revealDeadlineTick) + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && locals.currentTick > locals.cmt.revealDeadlineTick) { - uint64 lostDeposit = cmt.amount; - state.lostDepositsRevenue += lostDeposit; - state.totalRevenue += lostDeposit; - state.pendingShareholderDistribution += lostDeposit; - state.totalSecurityDepositsLocked -= lostDeposit; + // Move deposit into lost revenue and remove commitment (swap-with-last) + locals.lostDeposit = locals.cmt.amount; + state.lostDepositsRevenue += locals.lostDeposit; + state.totalRevenue += locals.lostDeposit; + state.pendingShareholderDistribution += locals.lostDeposit; + state.totalSecurityDepositsLocked -= locals.lostDeposit; if (locals.i != state.commitmentCount - 1) { - state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); } state.commitmentCount--; } @@ -292,19 +290,21 @@ struct RANDOM : public ContractBase } } - // Unclaimed deposits: return forcibly at end of tick + // Special-case early epoch: forcibly return deposits that expire exactly this tick if (qpi.numberOfTickTransactions() == -1) { for (locals.i = 0; locals.i < state.commitmentCount;) { - EntropyCommitment cmt = state.commitments.get(locals.i); - if (!cmt.hasRevealed && cmt.revealDeadlineTick == qpi.tick()) + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && locals.cmt.revealDeadlineTick == qpi.tick()) { - qpi.transfer(cmt.invocatorId, cmt.amount); - state.totalSecurityDepositsLocked -= cmt.amount; + // refund directly in early epoch mode + qpi.transfer(locals.cmt.invocatorId, locals.cmt.amount); + state.totalSecurityDepositsLocked -= locals.cmt.amount; if (locals.i != state.commitmentCount - 1) { - state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); } state.commitmentCount--; } @@ -316,45 +316,65 @@ struct RANDOM : public ContractBase return; } - locals.hasRevealData = !isZeroBitsCheck(input.revealedBits); + // Precompute digest of revealedBits once to avoid forbidden casts into bit_4096 + locals.revealedDigest = qpi.K12(input.revealedBits); + + // Presence of reveal is treated as an attempt to match stored digests + locals.hasRevealData = true; locals.hasNewCommit = !isZeroIdCheck(input.committedDigest); locals.isStoppingMining = (qpi.invocationReward() == 0); - // Reveal logic (return deposit, add entropy, update recent miner stats) + // If reveal provided: search for matching commitment(s) by this invocator if (locals.hasRevealData) { for (locals.i = 0; locals.i < state.commitmentCount;) { - EntropyCommitment cmt = state.commitments.get(locals.i); - if (!cmt.hasRevealed && isEqualIdCheck(cmt.invocatorId, qpi.invocator())) + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && isEqualIdCheck(locals.cmt.invocatorId, qpi.invocator())) { - locals.hashMatches = k12CommitmentMatches(qpi, input.revealedBits, cmt.digest); + // Compare stored digest to precomputed K12(revealedBits) + locals.hashMatches = (locals.cmt.digest == locals.revealedDigest); if (locals.hashMatches) { - if (locals.currentTick > cmt.revealDeadlineTick) + // If reveal too late, deposit is forfeited; otherwise update entropy pool and refund. + if (locals.currentTick > locals.cmt.revealDeadlineTick) { - uint64 lostDeposit = cmt.amount; - state.lostDepositsRevenue += lostDeposit; - state.totalRevenue += lostDeposit; - state.pendingShareholderDistribution += lostDeposit; + locals.lostDeposit = locals.cmt.amount; + state.lostDepositsRevenue += locals.lostDeposit; + state.totalRevenue += locals.lostDeposit; + state.pendingShareholderDistribution += locals.lostDeposit; output.revealSuccessful = false; } else { - updateEntropyPoolData(state, input.revealedBits); - qpi.transfer(qpi.invocator(), cmt.amount); + // Apply the 256-bit digest to the pool by XORing the 4 x 64-bit lanes. + // This avoids inspecting bit_4096 internals and keeps everything QPI-compliant. + state.currentEntropyPool.u64._0 ^= locals.revealedDigest.u64._0; + state.currentEntropyPool.u64._1 ^= locals.revealedDigest.u64._1; + state.currentEntropyPool.u64._2 ^= locals.revealedDigest.u64._2; + state.currentEntropyPool.u64._3 ^= locals.revealedDigest.u64._3; + + // Advance circular history with copy of new pool and bump version. + state.entropyHistoryHead = (state.entropyHistoryHead + 1) & (RANDOM_ENTROPY_HISTORY_LEN - 1); + state.entropyHistory.set(state.entropyHistoryHead, state.currentEntropyPool); + + state.entropyPoolVersion++; + state.entropyPoolVersionHistory.set(state.entropyHistoryHead, state.entropyPoolVersion); + + // Refund deposit to invocator and update stats. + qpi.transfer(qpi.invocator(), locals.cmt.amount); output.revealSuccessful = true; - output.depositReturned = cmt.amount; + output.depositReturned = locals.cmt.amount; state.totalReveals++; - state.totalSecurityDepositsLocked -= cmt.amount; + state.totalSecurityDepositsLocked -= locals.cmt.amount; - // Maintain LRU recentMiner list for rewards + // Maintain recentMiners LRU: update existing entry, append if space, or replace lowest. locals.existingIndex = -1; for (locals.rm = 0; locals.rm < state.recentMinerCount; ++locals.rm) { - RecentMiner rm = state.recentMiners.get(locals.rm); - if (isEqualIdCheck(rm.minerId, qpi.invocator())) + locals.recentMinerA = state.recentMiners.get(locals.rm); + if (isEqualIdCheck(locals.recentMinerA.minerId, qpi.invocator())) { locals.existingIndex = locals.rm; break; @@ -362,58 +382,61 @@ struct RANDOM : public ContractBase } if (locals.existingIndex >= 0) { - RecentMiner rm = state.recentMiners.get(locals.existingIndex); - if (rm.deposit < cmt.amount) + // update stored recent miner entry + locals.recentMinerA = state.recentMiners.get(locals.existingIndex); + if (locals.recentMinerA.deposit < locals.cmt.amount) { - rm.deposit = cmt.amount; - rm.lastEntropyVersion = state.entropyPoolVersion; + locals.recentMinerA.deposit = locals.cmt.amount; + locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; } - rm.lastRevealTick = locals.currentTick; - state.recentMiners.set(locals.existingIndex, rm); + locals.recentMinerA.lastRevealTick = locals.currentTick; + state.recentMiners.set(locals.existingIndex, locals.recentMinerA); } else if (state.recentMinerCount < RANDOM_MAX_RECENT_MINERS) { - RecentMiner rm; - rm.minerId = qpi.invocator(); - rm.deposit = cmt.amount; - rm.lastEntropyVersion = state.entropyPoolVersion; - rm.lastRevealTick = locals.currentTick; - state.recentMiners.set(state.recentMinerCount, rm); + // append new recent miner + locals.recentMinerA.minerId = qpi.invocator(); + locals.recentMinerA.deposit = locals.cmt.amount; + locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; + locals.recentMinerA.lastRevealTick = locals.currentTick; + state.recentMiners.set(state.recentMinerCount, locals.recentMinerA); state.recentMinerCount++; } else { + // Find lowest-ranked miner and replace if current qualifies locals.lowestIx = 0; for (locals.rm = 1; locals.rm < RANDOM_MAX_RECENT_MINERS; ++locals.rm) { - RecentMiner test = state.recentMiners.get(locals.rm); - RecentMiner lo = state.recentMiners.get(locals.lowestIx); - if (test.deposit < lo.deposit || - (test.deposit == lo.deposit && test.lastEntropyVersion < lo.lastEntropyVersion)) + locals.recentMinerA = state.recentMiners.get(locals.rm); + locals.recentMinerB = state.recentMiners.get(locals.lowestIx); + if (locals.recentMinerA.deposit < locals.recentMinerB.deposit || + (locals.recentMinerA.deposit == locals.recentMinerB.deposit && locals.recentMinerA.lastEntropyVersion < locals.recentMinerB.lastEntropyVersion)) { locals.lowestIx = locals.rm; } } - RecentMiner rm = state.recentMiners.get(locals.lowestIx); + locals.recentMinerA = state.recentMiners.get(locals.lowestIx); if ( - cmt.amount > rm.deposit || - (cmt.amount == rm.deposit && state.entropyPoolVersion > rm.lastEntropyVersion) + locals.cmt.amount > locals.recentMinerA.deposit || + (locals.cmt.amount == locals.recentMinerA.deposit && state.entropyPoolVersion > locals.recentMinerA.lastEntropyVersion) ) { - rm.minerId = qpi.invocator(); - rm.deposit = cmt.amount; - rm.lastEntropyVersion = state.entropyPoolVersion; - rm.lastRevealTick = locals.currentTick; - state.recentMiners.set(locals.lowestIx, rm); + locals.recentMinerA.minerId = qpi.invocator(); + locals.recentMinerA.deposit = locals.cmt.amount; + locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; + locals.recentMinerA.lastRevealTick = locals.currentTick; + state.recentMiners.set(locals.lowestIx, locals.recentMinerA); } } } - state.totalSecurityDepositsLocked -= cmt.amount; - + // Remove commitment (swap with last) and continue scanning without incrementing i. + state.totalSecurityDepositsLocked -= locals.cmt.amount; if (locals.i != state.commitmentCount - 1) { - state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); } state.commitmentCount--; continue; @@ -423,24 +446,33 @@ struct RANDOM : public ContractBase } } - // New commitment/registration for reward round + // If caller provided a new commitment (invocationReward used as deposit) and not stopping mining, + // accept it if deposit is valid and meets minimum. if (locals.hasNewCommit && !locals.isStoppingMining) { - if ( - isValidDepositAmountCheck(state, qpi.invocationReward()) && - qpi.invocationReward() >= state.minimumSecurityDeposit - ) + // Inline deposit validity check using allowed-values array + locals.depositValid = false; + for (locals.i = 0; locals.i < RANDOM_VALID_DEPOSIT_AMOUNTS; ++locals.i) + { + if (validDepositAmountAt(state, qpi.invocationReward(), locals.i)) + { + locals.depositValid = true; + break; + } + } + + if (locals.depositValid && qpi.invocationReward() >= state.minimumSecurityDeposit) { if (state.commitmentCount < RANDOM_MAX_COMMITMENTS) { - EntropyCommitment ncmt; - ncmt.digest = input.committedDigest; - ncmt.invocatorId = qpi.invocator(); - ncmt.amount = qpi.invocationReward(); - ncmt.commitTick = locals.currentTick; - ncmt.revealDeadlineTick = locals.currentTick + state.revealTimeoutTicks; - ncmt.hasRevealed = false; - state.commitments.set(state.commitmentCount, ncmt); + // Use locals.ncmt (approved locals) as temporary to avoid stack-local. + locals.ncmt.digest = input.committedDigest; + locals.ncmt.invocatorId = qpi.invocator(); + locals.ncmt.amount = qpi.invocationReward(); + locals.ncmt.commitTick = locals.currentTick; + locals.ncmt.revealDeadlineTick = locals.currentTick + state.revealTimeoutTicks; + locals.ncmt.hasRevealed = false; + state.commitments.set(state.commitmentCount, locals.ncmt); state.commitmentCount++; state.totalCommits++; state.totalSecurityDepositsLocked += qpi.invocationReward(); @@ -449,28 +481,55 @@ struct RANDOM : public ContractBase } } - generateRandomBytesData(state, output.randomBytes, 32, 0, locals.currentTick); + // Produce 32 random-like bytes from latest entropy history and current tick: + // - take most recent history entry (histIdx) and extract bytes from its 64-bit lanes, + // - XOR first 8 bytes with tick-derived bytes to add per-tick variation. + locals.histIdx = (state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 0) & (RANDOM_ENTROPY_HISTORY_LEN - 1); + for (locals.rb_i = 0; locals.rb_i < RANDOM_RANDOMBYTES_LEN; ++locals.rb_i) + { + // Extract the correct 64-bit lane and then the requested byte without using plain []. + output.randomBytes.set( + locals.rb_i, + static_cast( + ( + ( + (locals.rb_i < 8) ? state.entropyHistory.get(locals.histIdx).u64._0 : + (locals.rb_i < 16) ? state.entropyHistory.get(locals.histIdx).u64._1 : + (locals.rb_i < 24) ? state.entropyHistory.get(locals.histIdx).u64._2 : + state.entropyHistory.get(locals.histIdx).u64._3 + ) >> (8 * (locals.rb_i & 7)) + ) & 0xFF + ) ^ + (locals.rb_i < 8 ? static_cast((static_cast(locals.currentTick) >> (8 * locals.rb_i)) & 0xFF) : 0) + ); + } + output.entropyVersion = state.entropyPoolVersion; } + // BuyEntropy procedure: + // - Removes expired commitments (same sweep) + // - Checks buyer fee and miner eligibility + // - Charges buyer and returns requested bytes from slightly older pool version PUBLIC_PROCEDURE_WITH_LOCALS(BuyEntropy) { locals.currentTick = qpi.tick(); - // Housekeeping: remove expired commitments + // Sweep expired commitments (same logic as above) for (locals.i = 0; locals.i < state.commitmentCount;) { - EntropyCommitment cmt = state.commitments.get(locals.i); - if (!cmt.hasRevealed && locals.currentTick > cmt.revealDeadlineTick) + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && locals.currentTick > locals.cmt.revealDeadlineTick) { - uint64 lostDeposit = cmt.amount; - state.lostDepositsRevenue += lostDeposit; - state.totalRevenue += lostDeposit; - state.pendingShareholderDistribution += lostDeposit; - state.totalSecurityDepositsLocked -= lostDeposit; + locals.lostDeposit = locals.cmt.amount; + state.lostDepositsRevenue += locals.lostDeposit; + state.totalRevenue += locals.lostDeposit; + state.pendingShareholderDistribution += locals.lostDeposit; + state.totalSecurityDepositsLocked -= locals.lostDeposit; if (locals.i != state.commitmentCount - 1) { - state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); } state.commitmentCount--; } @@ -480,6 +539,7 @@ struct RANDOM : public ContractBase } } + // Disallow in early-epoch mode if (qpi.numberOfTickTransactions() == -1) { output.success = false; @@ -491,17 +551,15 @@ struct RANDOM : public ContractBase locals.eligible = false; locals.usedMinerDeposit = 0; - // Find eligible recent miner + // Find an eligible recent miner whose deposit >= minMinerDeposit and who revealed recently for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) { - RecentMiner rm = state.recentMiners.get(locals.i); - if ( - rm.deposit >= input.minMinerDeposit && - (locals.currentTick - rm.lastRevealTick) <= state.revealTimeoutTicks - ) + locals.recentMinerTemp = state.recentMiners.get(locals.i); + if (locals.recentMinerTemp.deposit >= input.minMinerDeposit && + (locals.currentTick - locals.recentMinerTemp.lastRevealTick) <= state.revealTimeoutTicks) { locals.eligible = true; - locals.usedMinerDeposit = rm.deposit; + locals.usedMinerDeposit = locals.recentMinerTemp.deposit; break; } } @@ -511,6 +569,7 @@ struct RANDOM : public ContractBase return; } + // Compute minimum price and check buyer fee locals.minPrice = state.pricePerByte * input.numberOfBytes * (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); @@ -519,36 +578,52 @@ struct RANDOM : public ContractBase return; } - locals.histIdx = (state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2) % RANDOM_ENTROPY_HISTORY_LEN; - generateRandomBytesData( - state, - output.randomBytes, - (input.numberOfBytes > 32 ? 32 : input.numberOfBytes), - locals.histIdx, - locals.currentTick - ); + // Use the previous-but-one history entry for purchased entropy (to avoid last-second reveals) + locals.histIdx = (state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2) & (RANDOM_ENTROPY_HISTORY_LEN - 1); + // Produce requested bytes (bounded by RANDOM_RANDOMBYTES_LEN) + for (locals.i = 0; locals.i < ((input.numberOfBytes > RANDOM_RANDOMBYTES_LEN) ? RANDOM_RANDOMBYTES_LEN : input.numberOfBytes); ++locals.i) + { + output.randomBytes.set( + locals.i, + static_cast( + ( + ( + (locals.i < 8) ? state.entropyHistory.get(locals.histIdx).u64._0 : + (locals.i < 16) ? state.entropyHistory.get(locals.histIdx).u64._1 : + (locals.i < 24) ? state.entropyHistory.get(locals.histIdx).u64._2 : + state.entropyHistory.get(locals.histIdx).u64._3 + ) >> (8 * (locals.i & 7)) + ) & 0xFF + ) ^ + (locals.i < 8 ? static_cast((static_cast(locals.currentTick) >> (8 * locals.i)) & 0xFF) : 0) + ); + } + + // Return metadata and split buyer fee output.entropyVersion = state.entropyPoolVersionHistory.get(locals.histIdx); output.usedMinerDeposit = locals.usedMinerDeposit; output.usedPoolVersion = state.entropyPoolVersionHistory.get(locals.histIdx); output.success = true; + // Split fee: half to miners pool, half to shareholders locals.half = div(locals.buyerFee, (uint64)2); state.minerEarningsPool += locals.half; state.shareholderEarningsPool += (locals.buyerFee - locals.half); } - PUBLIC_FUNCTION(GetContractInfo) + // GetContractInfo: return public state summary + PUBLIC_FUNCTION_WITH_LOCALS(GetContractInfo) { - uint32 currentTick = qpi.tick(); - uint32 activeCount = 0; + locals.currentTick = qpi.tick(); + locals.activeCount = 0; output.totalCommits = state.totalCommits; output.totalReveals = state.totalReveals; output.totalSecurityDepositsLocked = state.totalSecurityDepositsLocked; output.minimumSecurityDeposit = state.minimumSecurityDeposit; output.revealTimeoutTicks = state.revealTimeoutTicks; - output.currentTick = currentTick; + output.currentTick = locals.currentTick; output.entropyPoolVersion = state.entropyPoolVersion; output.totalRevenue = state.totalRevenue; @@ -558,62 +633,72 @@ struct RANDOM : public ContractBase output.shareholderEarningsPool = state.shareholderEarningsPool; output.recentMinerCount = state.recentMinerCount; - for (uint32 i = 0; i < 16; ++i) + // Copy valid deposit amounts + for (locals.i = 0; locals.i < RANDOM_VALID_DEPOSIT_AMOUNTS; ++locals.i) { - output.validDepositAmounts[i] = state.validDepositAmounts[i]; + output.validDepositAmounts.set(locals.i, state.validDepositAmounts.get(locals.i)); } - for (uint32 i = 0; i < state.commitmentCount; ++i) + // Count active commitments + for (locals.i = 0; locals.i < state.commitmentCount; ++locals.i) { - if (!state.commitments.get(i).hasRevealed) + if (!state.commitments.get(locals.i).hasRevealed) { - activeCount++; + locals.activeCount++; } } - output.activeCommitments = activeCount; + output.activeCommitments = locals.activeCount; } - PUBLIC_FUNCTION(GetUserCommitments) + // GetUserCommitments: list commitments for a user (bounded) + PUBLIC_FUNCTION_WITH_LOCALS(GetUserCommitments) { - uint32 userCommitmentCount = 0; - for (uint32 i = 0; i < state.commitmentCount && userCommitmentCount < 32; i++) + locals.userCommitmentCount = 0; + for (locals.i = 0; locals.i < state.commitmentCount && locals.userCommitmentCount < RANDOM_MAX_USER_COMMITMENTS; ++locals.i) { - EntropyCommitment cmt = state.commitments.get(i); - if (isEqualIdCheck(cmt.invocatorId, input.userId)) + locals.cmt = state.commitments.get(locals.i); + if (isEqualIdCheck(locals.cmt.invocatorId, input.userId)) { - output.commitments[userCommitmentCount].digest = cmt.digest; - output.commitments[userCommitmentCount].amount = cmt.amount; - output.commitments[userCommitmentCount].commitTick = cmt.commitTick; - output.commitments[userCommitmentCount].revealDeadlineTick = cmt.revealDeadlineTick; - output.commitments[userCommitmentCount].hasRevealed = cmt.hasRevealed; - userCommitmentCount++; + // copy to output buffer + locals.ucmt.digest = locals.cmt.digest; + locals.ucmt.amount = locals.cmt.amount; + locals.ucmt.commitTick = locals.cmt.commitTick; + locals.ucmt.revealDeadlineTick = locals.cmt.revealDeadlineTick; + locals.ucmt.hasRevealed = locals.cmt.hasRevealed; + output.commitments.set(locals.userCommitmentCount, locals.ucmt); + locals.userCommitmentCount++; } } - output.commitmentCount = userCommitmentCount; + output.commitmentCount = locals.userCommitmentCount; } + // QueryPrice: compute price for a buyer based on requested bytes and min miner deposit PUBLIC_FUNCTION(QueryPrice) { output.price = state.pricePerByte * input.numberOfBytes * (div(input.minMinerDeposit, (uint64)state.priceDepositDivisor) + 1); } + // END_EPOCH: sweep expired commitments and distribute earnings to recent miners and shareholders END_EPOCH_WITH_LOCALS() { locals.currentTick = qpi.tick(); + + // Sweep expired commitments (same logic) for (locals.i = 0; locals.i < state.commitmentCount;) { - EntropyCommitment cmt = state.commitments.get(locals.i); - if (!cmt.hasRevealed && locals.currentTick > cmt.revealDeadlineTick) + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && locals.currentTick > locals.cmt.revealDeadlineTick) { - uint64 lostDeposit = cmt.amount; - state.lostDepositsRevenue += lostDeposit; - state.totalRevenue += lostDeposit; - state.pendingShareholderDistribution += lostDeposit; - state.totalSecurityDepositsLocked -= lostDeposit; + locals.lostDeposit = locals.cmt.amount; + state.lostDepositsRevenue += locals.lostDeposit; + state.totalRevenue += locals.lostDeposit; + state.pendingShareholderDistribution += locals.lostDeposit; + state.totalSecurityDepositsLocked -= locals.lostDeposit; if (locals.i != state.commitmentCount - 1) { - state.commitments.set(locals.i, state.commitments.get(state.commitmentCount - 1)); + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); } state.commitmentCount--; } @@ -623,29 +708,37 @@ struct RANDOM : public ContractBase } } + // Pay miners equally from minerEarningsPool if (state.minerEarningsPool > 0 && state.recentMinerCount > 0) { locals.payout = div(state.minerEarningsPool, (uint64)state.recentMinerCount); for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) { - RecentMiner rm = state.recentMiners.get(locals.i); - if (!isZeroIdCheck(rm.minerId)) + locals.recentMinerTemp = state.recentMiners.get(locals.i); + if (!isZeroIdCheck(locals.recentMinerTemp.minerId)) { - qpi.transfer(rm.minerId, locals.payout); + qpi.transfer(locals.recentMinerTemp.minerId, locals.payout); } } + // reset miner pool and recentMiners state.minerEarningsPool = 0; for (locals.i = 0; locals.i < RANDOM_MAX_RECENT_MINERS; ++locals.i) { - state.recentMiners.set(locals.i, RecentMiner{}); + locals.recentMinerTemp.minerId = id::zero(); + locals.recentMinerTemp.deposit = 0; + locals.recentMinerTemp.lastEntropyVersion = 0; + locals.recentMinerTemp.lastRevealTick = 0; + state.recentMiners.set(locals.i, locals.recentMinerTemp); } state.recentMinerCount = 0; } + // Distribute shareholder earnings (if any) if (state.shareholderEarningsPool > 0) { qpi.distributeDividends(div(state.shareholderEarningsPool, (uint64)NUMBER_OF_COMPUTORS)); state.shareholderEarningsPool = 0; } + // Distribute any pending shareholder distribution (from lost deposits) if (state.pendingShareholderDistribution > 0) { qpi.distributeDividends(div(state.pendingShareholderDistribution, (uint64)NUMBER_OF_COMPUTORS)); @@ -653,6 +746,7 @@ struct RANDOM : public ContractBase } } + // Register functions and procedures (standard QPI boilerplate) REGISTER_USER_FUNCTIONS_AND_PROCEDURES() { REGISTER_USER_FUNCTION(GetContractInfo, 1); @@ -663,21 +757,28 @@ struct RANDOM : public ContractBase REGISTER_USER_PROCEDURE(BuyEntropy, 2); } - INITIALIZE() + // INITIALIZE: set defaults and fill valid deposit amounts array (powers of 10) + INITIALIZE_WITH_LOCALS() { + locals.i = 0; + locals.j = 0; + locals.val = 0; + state.entropyHistoryHead = 0; state.minimumSecurityDeposit = 1; state.revealTimeoutTicks = 9; state.pricePerByte = 10; state.priceDepositDivisor = 1000; - for (uint32 i = 0; i < 16; ++i) + // validDepositAmounts: 1, 10, 100, 1000, ... + for (locals.i = 0; locals.i < RANDOM_VALID_DEPOSIT_AMOUNTS; ++locals.i) { - state.validDepositAmounts[i] = 1ULL; - for (uint32 j = 0; j < i; ++j) + locals.val = 1ULL; + for (locals.j = 0; locals.j < locals.i; ++locals.j) { - state.validDepositAmounts[i] *= 10; + locals.val *= 10; } + state.validDepositAmounts.set(locals.i, locals.val); } } }; From ba83dfa992820e2d96e25a5ef0b3e1aa551eb048 Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Mon, 22 Dec 2025 14:59:22 +0100 Subject: [PATCH 10/12] Refactor BuyEntropy procedure for clarity and efficiency --- src/contracts/Random.h | 197 +++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 97 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index 98e804818..b9c8a11bc 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -513,103 +513,106 @@ struct RANDOM : public ContractBase // - Charges buyer and returns requested bytes from slightly older pool version PUBLIC_PROCEDURE_WITH_LOCALS(BuyEntropy) { - locals.currentTick = qpi.tick(); - - // Sweep expired commitments (same logic as above) - for (locals.i = 0; locals.i < state.commitmentCount;) - { - locals.cmt = state.commitments.get(locals.i); - if (!locals.cmt.hasRevealed && locals.currentTick > locals.cmt.revealDeadlineTick) - { - locals.lostDeposit = locals.cmt.amount; - state.lostDepositsRevenue += locals.lostDeposit; - state.totalRevenue += locals.lostDeposit; - state.pendingShareholderDistribution += locals.lostDeposit; - state.totalSecurityDepositsLocked -= locals.lostDeposit; - if (locals.i != state.commitmentCount - 1) - { - locals.cmt = state.commitments.get(state.commitmentCount - 1); - state.commitments.set(locals.i, locals.cmt); - } - state.commitmentCount--; - } - else - { - locals.i++; - } - } - - // Disallow in early-epoch mode - if (qpi.numberOfTickTransactions() == -1) - { - output.success = false; - return; - } - - output.success = false; - locals.buyerFee = qpi.invocationReward(); - locals.eligible = false; - locals.usedMinerDeposit = 0; - - // Find an eligible recent miner whose deposit >= minMinerDeposit and who revealed recently - for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) - { - locals.recentMinerTemp = state.recentMiners.get(locals.i); - if (locals.recentMinerTemp.deposit >= input.minMinerDeposit && - (locals.currentTick - locals.recentMinerTemp.lastRevealTick) <= state.revealTimeoutTicks) - { - locals.eligible = true; - locals.usedMinerDeposit = locals.recentMinerTemp.deposit; - break; - } - } - - if (!locals.eligible) - { - return; - } - - // Compute minimum price and check buyer fee - locals.minPrice = state.pricePerByte * input.numberOfBytes * - (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); - - if (locals.buyerFee < locals.minPrice) - { - return; - } - - // Use the previous-but-one history entry for purchased entropy (to avoid last-second reveals) - locals.histIdx = (state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2) & (RANDOM_ENTROPY_HISTORY_LEN - 1); - - // Produce requested bytes (bounded by RANDOM_RANDOMBYTES_LEN) - for (locals.i = 0; locals.i < ((input.numberOfBytes > RANDOM_RANDOMBYTES_LEN) ? RANDOM_RANDOMBYTES_LEN : input.numberOfBytes); ++locals.i) - { - output.randomBytes.set( - locals.i, - static_cast( - ( - ( - (locals.i < 8) ? state.entropyHistory.get(locals.histIdx).u64._0 : - (locals.i < 16) ? state.entropyHistory.get(locals.histIdx).u64._1 : - (locals.i < 24) ? state.entropyHistory.get(locals.histIdx).u64._2 : - state.entropyHistory.get(locals.histIdx).u64._3 - ) >> (8 * (locals.i & 7)) - ) & 0xFF - ) ^ - (locals.i < 8 ? static_cast((static_cast(locals.currentTick) >> (8 * locals.i)) & 0xFF) : 0) - ); - } - - // Return metadata and split buyer fee - output.entropyVersion = state.entropyPoolVersionHistory.get(locals.histIdx); - output.usedMinerDeposit = locals.usedMinerDeposit; - output.usedPoolVersion = state.entropyPoolVersionHistory.get(locals.histIdx); - output.success = true; - - // Split fee: half to miners pool, half to shareholders - locals.half = div(locals.buyerFee, (uint64)2); - state.minerEarningsPool += locals.half; - state.shareholderEarningsPool += (locals.buyerFee - locals.half); + locals.currentTick = qpi.tick(); + + // Sweep expired commitments + for (locals.i = 0; locals.i < state.commitmentCount;) + { + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && locals.currentTick > locals.cmt.revealDeadlineTick) + { + locals.lostDeposit = locals.cmt.amount; + state.lostDepositsRevenue += locals.lostDeposit; + state.totalRevenue += locals.lostDeposit; + state.pendingShareholderDistribution += locals.lostDeposit; + state.totalSecurityDepositsLocked -= locals.lostDeposit; + if (locals.i != state.commitmentCount - 1) + { + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); + } + state.commitmentCount--; + } + else + { + locals.i++; + } + } + + // Disallow in early-epoch mode -- refund buyer + if (qpi.numberOfTickTransactions() == -1) + { + output.success = false; + qpi.transfer(qpi.invocator(), qpi.invocationReward()); // <-- refund buyer + return; + } + + output.success = false; + locals.buyerFee = qpi.invocationReward(); + locals.eligible = false; + locals.usedMinerDeposit = 0; + + // Find an eligible recent miner whose deposit >= minMinerDeposit and who revealed recently + for (locals.i = 0; locals.i < state.recentMinerCount; ++locals.i) + { + locals.recentMinerTemp = state.recentMiners.get(locals.i); + if (locals.recentMinerTemp.deposit >= input.minMinerDeposit && + (locals.currentTick - locals.recentMinerTemp.lastRevealTick) <= state.revealTimeoutTicks) + { + locals.eligible = true; + locals.usedMinerDeposit = locals.recentMinerTemp.deposit; + break; + } + } + + if (!locals.eligible) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); // <-- refund buyer (no entropy available) + return; + } + + // Compute minimum price and check buyer fee + locals.minPrice = state.pricePerByte * input.numberOfBytes * + (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); + + if (locals.buyerFee < locals.minPrice) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); // <-- refund buyer (not enough fee) + return; + } + + // Use the previous-but-one history entry for purchased entropy (to avoid last-second reveals) + locals.histIdx = (state.entropyHistoryHead + RANDOM_ENTROPY_HISTORY_LEN - 2) & (RANDOM_ENTROPY_HISTORY_LEN - 1); + + // Produce requested bytes (bounded by RANDOM_RANDOMBYTES_LEN) + for (locals.i = 0; locals.i < ((input.numberOfBytes > RANDOM_RANDOMBYTES_LEN) ? RANDOM_RANDOMBYTES_LEN : input.numberOfBytes); ++locals.i) + { + output.randomBytes.set( + locals.i, + static_cast( + ( + ( + (locals.i < 8) ? state.entropyHistory.get(locals.histIdx).u64._0 : + (locals.i < 16) ? state.entropyHistory.get(locals.histIdx).u64._1 : + (locals.i < 24) ? state.entropyHistory.get(locals.histIdx).u64._2 : + state.entropyHistory.get(locals.histIdx).u64._3 + ) >> (8 * (locals.i & 7)) + ) & 0xFF + ) ^ + (locals.i < 8 ? static_cast((static_cast(locals.currentTick) >> (8 * locals.i)) & 0xFF) : 0) + ); + } + + // Return entropy pool/version info and signal success + output.entropyVersion = state.entropyPoolVersionHistory.get(locals.histIdx); + output.usedMinerDeposit = locals.usedMinerDeposit; + output.usedPoolVersion = state.entropyPoolVersionHistory.get(locals.histIdx); + output.success = true; + + // Split fee: half to miners pool, half to shareholders + locals.half = div(locals.buyerFee, (uint64)2); + state.minerEarningsPool += locals.half; + state.shareholderEarningsPool += (locals.buyerFee - locals.half); } // GetContractInfo: return public state summary From 310d4c0a2f0a4ef1b7c897d9bbf08aa2848e4a2f Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Mon, 22 Dec 2025 15:17:13 +0100 Subject: [PATCH 11/12] Comments --- src/contracts/Random.h | 204 ++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 105 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index b9c8a11bc..83aef886d 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -325,125 +325,122 @@ struct RANDOM : public ContractBase locals.isStoppingMining = (qpi.invocationReward() == 0); // If reveal provided: search for matching commitment(s) by this invocator - if (locals.hasRevealData) + for (locals.i = 0; locals.i < state.commitmentCount;) { - for (locals.i = 0; locals.i < state.commitmentCount;) + locals.cmt = state.commitments.get(locals.i); + if (!locals.cmt.hasRevealed && isEqualIdCheck(locals.cmt.invocatorId, qpi.invocator())) { - locals.cmt = state.commitments.get(locals.i); - if (!locals.cmt.hasRevealed && isEqualIdCheck(locals.cmt.invocatorId, qpi.invocator())) - { - // Compare stored digest to precomputed K12(revealedBits) - locals.hashMatches = (locals.cmt.digest == locals.revealedDigest); + // Compare stored digest to precomputed K12(revealedBits) + locals.hashMatches = (locals.cmt.digest == locals.revealedDigest); - if (locals.hashMatches) + if (locals.hashMatches) + { + // If reveal too late, deposit is forfeited; otherwise update entropy pool and refund. + if (locals.currentTick > locals.cmt.revealDeadlineTick) { - // If reveal too late, deposit is forfeited; otherwise update entropy pool and refund. - if (locals.currentTick > locals.cmt.revealDeadlineTick) + locals.lostDeposit = locals.cmt.amount; + state.lostDepositsRevenue += locals.lostDeposit; + state.totalRevenue += locals.lostDeposit; + state.pendingShareholderDistribution += locals.lostDeposit; + output.revealSuccessful = false; + } + else + { + // Apply the 256-bit digest to the pool by XORing the 4 x 64-bit lanes. + // This avoids inspecting bit_4096 internals and keeps everything QPI-compliant. + state.currentEntropyPool.u64._0 ^= locals.revealedDigest.u64._0; + state.currentEntropyPool.u64._1 ^= locals.revealedDigest.u64._1; + state.currentEntropyPool.u64._2 ^= locals.revealedDigest.u64._2; + state.currentEntropyPool.u64._3 ^= locals.revealedDigest.u64._3; + + // Advance circular history with copy of new pool and bump version. + state.entropyHistoryHead = (state.entropyHistoryHead + 1) & (RANDOM_ENTROPY_HISTORY_LEN - 1); + state.entropyHistory.set(state.entropyHistoryHead, state.currentEntropyPool); + + state.entropyPoolVersion++; + state.entropyPoolVersionHistory.set(state.entropyHistoryHead, state.entropyPoolVersion); + + // Refund deposit to invocator and update stats. + qpi.transfer(qpi.invocator(), locals.cmt.amount); + output.revealSuccessful = true; + output.depositReturned = locals.cmt.amount; + state.totalReveals++; + state.totalSecurityDepositsLocked -= locals.cmt.amount; + + // Maintain recentMiners LRU: update existing entry, append if space, or replace lowest. + locals.existingIndex = -1; + for (locals.rm = 0; locals.rm < state.recentMinerCount; ++locals.rm) { - locals.lostDeposit = locals.cmt.amount; - state.lostDepositsRevenue += locals.lostDeposit; - state.totalRevenue += locals.lostDeposit; - state.pendingShareholderDistribution += locals.lostDeposit; - output.revealSuccessful = false; + locals.recentMinerA = state.recentMiners.get(locals.rm); + if (isEqualIdCheck(locals.recentMinerA.minerId, qpi.invocator())) + { + locals.existingIndex = locals.rm; + break; + } } - else + if (locals.existingIndex >= 0) { - // Apply the 256-bit digest to the pool by XORing the 4 x 64-bit lanes. - // This avoids inspecting bit_4096 internals and keeps everything QPI-compliant. - state.currentEntropyPool.u64._0 ^= locals.revealedDigest.u64._0; - state.currentEntropyPool.u64._1 ^= locals.revealedDigest.u64._1; - state.currentEntropyPool.u64._2 ^= locals.revealedDigest.u64._2; - state.currentEntropyPool.u64._3 ^= locals.revealedDigest.u64._3; - - // Advance circular history with copy of new pool and bump version. - state.entropyHistoryHead = (state.entropyHistoryHead + 1) & (RANDOM_ENTROPY_HISTORY_LEN - 1); - state.entropyHistory.set(state.entropyHistoryHead, state.currentEntropyPool); - - state.entropyPoolVersion++; - state.entropyPoolVersionHistory.set(state.entropyHistoryHead, state.entropyPoolVersion); - - // Refund deposit to invocator and update stats. - qpi.transfer(qpi.invocator(), locals.cmt.amount); - output.revealSuccessful = true; - output.depositReturned = locals.cmt.amount; - state.totalReveals++; - state.totalSecurityDepositsLocked -= locals.cmt.amount; - - // Maintain recentMiners LRU: update existing entry, append if space, or replace lowest. - locals.existingIndex = -1; - for (locals.rm = 0; locals.rm < state.recentMinerCount; ++locals.rm) + // update stored recent miner entry + locals.recentMinerA = state.recentMiners.get(locals.existingIndex); + if (locals.recentMinerA.deposit < locals.cmt.amount) { - locals.recentMinerA = state.recentMiners.get(locals.rm); - if (isEqualIdCheck(locals.recentMinerA.minerId, qpi.invocator())) - { - locals.existingIndex = locals.rm; - break; - } + locals.recentMinerA.deposit = locals.cmt.amount; + locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; } - if (locals.existingIndex >= 0) + locals.recentMinerA.lastRevealTick = locals.currentTick; + state.recentMiners.set(locals.existingIndex, locals.recentMinerA); + } + else if (state.recentMinerCount < RANDOM_MAX_RECENT_MINERS) + { + // append new recent miner + locals.recentMinerA.minerId = qpi.invocator(); + locals.recentMinerA.deposit = locals.cmt.amount; + locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; + locals.recentMinerA.lastRevealTick = locals.currentTick; + state.recentMiners.set(state.recentMinerCount, locals.recentMinerA); + state.recentMinerCount++; + } + else + { + // Find lowest-ranked miner and replace if current qualifies + locals.lowestIx = 0; + for (locals.rm = 1; locals.rm < RANDOM_MAX_RECENT_MINERS; ++locals.rm) { - // update stored recent miner entry - locals.recentMinerA = state.recentMiners.get(locals.existingIndex); - if (locals.recentMinerA.deposit < locals.cmt.amount) + locals.recentMinerA = state.recentMiners.get(locals.rm); + locals.recentMinerB = state.recentMiners.get(locals.lowestIx); + if (locals.recentMinerA.deposit < locals.recentMinerB.deposit || + (locals.recentMinerA.deposit == locals.recentMinerB.deposit && locals.recentMinerA.lastEntropyVersion < locals.recentMinerB.lastEntropyVersion)) { - locals.recentMinerA.deposit = locals.cmt.amount; - locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; + locals.lowestIx = locals.rm; } - locals.recentMinerA.lastRevealTick = locals.currentTick; - state.recentMiners.set(locals.existingIndex, locals.recentMinerA); } - else if (state.recentMinerCount < RANDOM_MAX_RECENT_MINERS) + locals.recentMinerA = state.recentMiners.get(locals.lowestIx); + if ( + locals.cmt.amount > locals.recentMinerA.deposit || + (locals.cmt.amount == locals.recentMinerA.deposit && state.entropyPoolVersion > locals.recentMinerA.lastEntropyVersion) + ) { - // append new recent miner locals.recentMinerA.minerId = qpi.invocator(); locals.recentMinerA.deposit = locals.cmt.amount; locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; locals.recentMinerA.lastRevealTick = locals.currentTick; - state.recentMiners.set(state.recentMinerCount, locals.recentMinerA); - state.recentMinerCount++; - } - else - { - // Find lowest-ranked miner and replace if current qualifies - locals.lowestIx = 0; - for (locals.rm = 1; locals.rm < RANDOM_MAX_RECENT_MINERS; ++locals.rm) - { - locals.recentMinerA = state.recentMiners.get(locals.rm); - locals.recentMinerB = state.recentMiners.get(locals.lowestIx); - if (locals.recentMinerA.deposit < locals.recentMinerB.deposit || - (locals.recentMinerA.deposit == locals.recentMinerB.deposit && locals.recentMinerA.lastEntropyVersion < locals.recentMinerB.lastEntropyVersion)) - { - locals.lowestIx = locals.rm; - } - } - locals.recentMinerA = state.recentMiners.get(locals.lowestIx); - if ( - locals.cmt.amount > locals.recentMinerA.deposit || - (locals.cmt.amount == locals.recentMinerA.deposit && state.entropyPoolVersion > locals.recentMinerA.lastEntropyVersion) - ) - { - locals.recentMinerA.minerId = qpi.invocator(); - locals.recentMinerA.deposit = locals.cmt.amount; - locals.recentMinerA.lastEntropyVersion = state.entropyPoolVersion; - locals.recentMinerA.lastRevealTick = locals.currentTick; - state.recentMiners.set(locals.lowestIx, locals.recentMinerA); - } + state.recentMiners.set(locals.lowestIx, locals.recentMinerA); } } + } - // Remove commitment (swap with last) and continue scanning without incrementing i. - state.totalSecurityDepositsLocked -= locals.cmt.amount; - if (locals.i != state.commitmentCount - 1) - { - locals.cmt = state.commitments.get(state.commitmentCount - 1); - state.commitments.set(locals.i, locals.cmt); - } - state.commitmentCount--; - continue; + // Remove commitment (swap with last) and continue scanning without incrementing i. + state.totalSecurityDepositsLocked -= locals.cmt.amount; + if (locals.i != state.commitmentCount - 1) + { + locals.cmt = state.commitments.get(state.commitmentCount - 1); + state.commitments.set(locals.i, locals.cmt); } + state.commitmentCount--; + continue; } - locals.i++; } + locals.i++; } // If caller provided a new commitment (invocationReward used as deposit) and not stopping mining, @@ -735,17 +732,14 @@ struct RANDOM : public ContractBase } state.recentMinerCount = 0; } - // Distribute shareholder earnings (if any) - if (state.shareholderEarningsPool > 0) - { - qpi.distributeDividends(div(state.shareholderEarningsPool, (uint64)NUMBER_OF_COMPUTORS)); - state.shareholderEarningsPool = 0; - } - // Distribute any pending shareholder distribution (from lost deposits) - if (state.pendingShareholderDistribution > 0) + + // Distribute any pending shareholder distribution (from earnings and/or lost deposits) + uint64 totalShareholderPayout = state.shareholderEarningsPool + state.pendingShareholderDistribution; + if (totalShareholderPayout > 0) { - qpi.distributeDividends(div(state.pendingShareholderDistribution, (uint64)NUMBER_OF_COMPUTORS)); - state.pendingShareholderDistribution = 0; + qpi.distributeDividends(div(totalShareholderPayout, (uint64)NUMBER_OF_COMPUTORS)); + state.shareholderEarningsPool = 0; + state.pendingShareholderDistribution = 0; } } From 5b30bf8681c58a194308ecc1a9b4f432cc1a4ddb Mon Sep 17 00:00:00 2001 From: ThatsNotMySourceCode Date: Mon, 22 Dec 2025 16:02:56 +0100 Subject: [PATCH 12/12] Refactor based on review --- src/contracts/Random.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/contracts/Random.h b/src/contracts/Random.h index 83aef886d..8ed3967a3 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -97,6 +97,12 @@ struct RANDOM : public ContractBase { return isZero(value); } + + static inline uint64 calculatePrice(const RANDOM& state, uint32 numberOfBytes, uint64 minMinerDeposit) + { + return state.pricePerByte * numberOfBytes * + (div(minMinerDeposit, state.priceDepositDivisor) + 1ULL); + } public: // --- Inputs / outputs for user-facing procedures and functions --- @@ -569,8 +575,7 @@ struct RANDOM : public ContractBase } // Compute minimum price and check buyer fee - locals.minPrice = state.pricePerByte * input.numberOfBytes * - (div(input.minMinerDeposit, state.priceDepositDivisor) + 1); + locals.minPrice = calculatePrice(state, input.numberOfBytes, input.minMinerDeposit); if (locals.buyerFee < locals.minPrice) { @@ -607,7 +612,7 @@ struct RANDOM : public ContractBase output.success = true; // Split fee: half to miners pool, half to shareholders - locals.half = div(locals.buyerFee, (uint64)2); + locals.half = div(locals.buyerFee, 2ULL); state.minerEarningsPool += locals.half; state.shareholderEarningsPool += (locals.buyerFee - locals.half); } @@ -634,10 +639,8 @@ struct RANDOM : public ContractBase output.recentMinerCount = state.recentMinerCount; // Copy valid deposit amounts - for (locals.i = 0; locals.i < RANDOM_VALID_DEPOSIT_AMOUNTS; ++locals.i) - { - output.validDepositAmounts.set(locals.i, state.validDepositAmounts.get(locals.i)); - } + copyMemory(output.validDepositAmounts, state.validDepositAmounts); + // Count active commitments for (locals.i = 0; locals.i < state.commitmentCount; ++locals.i) { @@ -674,8 +677,7 @@ struct RANDOM : public ContractBase // QueryPrice: compute price for a buyer based on requested bytes and min miner deposit PUBLIC_FUNCTION(QueryPrice) { - output.price = state.pricePerByte * input.numberOfBytes * - (div(input.minMinerDeposit, (uint64)state.priceDepositDivisor) + 1); + output.price = calculatePrice(state, input.numberOfBytes, input.minMinerDeposit); } // END_EPOCH: sweep expired commitments and distribute earnings to recent miners and shareholders