Transaction Details
SMART CONTRACT
Transaction Hash
9d4d84c65853769658ea5074ccbaf9c823993af617ae351875cd84ff383faaf6
Block Status
Transaction Information
Root Hash
Built Height
7685738
Block
Timestamp
1790660012163
Timestamp (UTC)
2026-09-29 05:33:32
Block Age
5h 12m ago
Fee
0.44487
TX Size
30.562 kB
Version
1
Confirmations
1005
Signature Type
DERO_HOMOMORPHIC
DERO Asset
2
Ring Size
0.44487
Fees
0.00000
Deposited
Sender
dero1qy2gyktqak3vqxl6k80g5at50z92ltj0xyyj4j8j22c5s2zjjpncsqq9wwyly
Ring Members (2)
Smart Contract Details
SC Balance
0.00000 DERO
Smart Contract Arguments
| Name | Type | Value |
|---|---|---|
| SC_ACTION | uint64 | 1 |
| SC_CODE | string | "// ============================================================================= // DERO VOTE — DVM-BASIC commit-reveal voting contract (binary masthead) // All artwork is comment-only and never executes. // ============================================================================= // 00000 00000 00000 00000 000 000 00000 0000000 00000 // 000 00 000 000 00 000 00 000 000 000 00 000 000 // 000 0 000 000 00 000 00 000 000 000 00 000 000 // 000 0 00000 00000 000 00 000 000 000 00 000 00000 // 000 0 000 000 0 000 00 000 000 000 00 000 000 // 000 00 000 000 0 000 00 00000 000 00 000 000 // 00000 00000 000 0 00000 000 00000 000 00000 // ============================================================================= // ============================================================================= // DeroVote V2 — DVM-BASIC commit-reveal voting contract // // PRIVACY MODEL — READ THIS FIRST // ---------------------------------------------------------------------------- // V2 hides a ballot only DURING the commit/voting phase. It is NOT // cryptographically private voting with a permanently hidden ballot. // // * COMMIT the voter publishes commitment = SHA256("DEROVOTE_V2_COMMIT|" // pollID|optionIndex|secret). The option and the secret are NEVER // sent to the chain, only the 64-char hex digest. Nothing on-chain // reveals which option was chosen. // * REVEAL after voting closes the voter publishes optionIndex + secret. // The contract recomputes the digest and compares it. From that // moment the vote is PERMANENTLY PUBLIC and linkable to the voter. // // This is the strongest ballot secrecy the DERO DVM can offer: the chain has // no encryption, no ZK verifier, no signature verification and no anonymous // credentials, so a tally that is both hidden and self-verifying is not // buildable here. Never describe this protocol as "private voting" without // the commit/reveal qualifier. // // PROTOCOL // 1. CreatePoll creator pays the creation fee; question and option labels // are stored ON-CHAIN and read back by the frontend from // contract state. 0-based option indexing. // 2. EnterPoll voter locks exactly requiredCollateral and receives one // poll-scoped, non-transferable Poll Voting Token. // 3. CommitVote during the voting phase the voter commits a hash. The // token becomes COMMITTED and can never be re-committed. // 4. RevealVote during the reveal phase the voter discloses option+secret. // The digest is verified; on success the option is tallied // and the token becomes USED. // 5. Refund once the reveal window has closed, each voter reclaims // exactly their locked collateral. // // POLL LIFECYCLE (permissionless, time-driven, monotonic) // VOTING -> REVEALING -> FINALIZED -> refunds open // // MONEY // All amounts are DERO atomic units. 1 DERO = 100000 atoms. // No floating point is used anywhere. // // LIMITS (enforced here AND mirrored in the frontend) // question 1..200 chars, optionCount 2..20, each option 1..100 chars, // votingDays 1..365, revealDays 1..30, collateral 100..1,000,000 DERO. // // DVM RUNTIME RULES THIS CODE RELIES ON (each verified on the real interpreter) // - LOAD() on a never-STOREd key PANICS and discards the tx, so every // possibly-fresh key is EXISTS-guarded. // - RETURN 0 commits; RETURN != 0 discards EVERY change atomically. A // non-zero RETURN was probed and confirmed to roll back prior STOREs in // the same invocation, which is what lets CreatePoll store as it validates. // - IF is the goto form ONLY: IF <expr> THEN GOTO n [ELSE GOTO m]. There is // no block IF / END IF. // - Uint64 arithmetic wraps silently, so every growth path is capped. // - Only // and /* */ comments are used; ' starts a char literal. // - String + Uint64 concatenation is supported and yields a String. // - A function returns only a Uint64 code; it cannot return a String, which // is why option labels are stored inline rather than via a helper. // - HEX(SHA256(s)) is byte-identical to a frontend SHA-256 hex digest, so // the commitment is computed OFF-CHAIN and only the hash is transmitted. // - Transaction SCDATA is part of the hashed, published transaction, so any // option or secret passed as a call argument would be public. CommitVote // therefore accepts ONLY the pre-computed digest. // // POLL VOTING TOKEN (see docs/VOTE-TOKEN.md) // An INTERNAL contract balance keyed by (pollID, voter). DERO native assets // are transferable, so a literal soulbound token cannot be enforced by the // chain; the right is non-transferable BY CONSTRUCTION because no function // accepts a token as an argument and every read is keyed by SIGNER(). Its // lifecycle is strictly monotonic 0 -> 1 ACTIVE -> 2 COMMITTED -> 3 USED, // so a committed vote can never be changed. // ============================================================================= Function Initialize() Uint64 10 STORE("owner", SIGNER()) 20 STORE("poll_count", 0) 30 STORE("fee_balance", 0) 40 RETURN 0 End Function // ----------------------------------------------------------------------------- // FeeBasisPoints(days) : deterministic, integer-only creation-fee schedule. // days <= 7 -> 1.00% (100 bps) // each extra started 7-day period -> +0.50% (+50 bps) // The frontend reproduces this exactly; it is pure integer arithmetic. // ----------------------------------------------------------------------------- Function FeeBasisPoints(days Uint64) Uint64 10 DIM weeks, bps AS Uint64 20 IF days <= 7 THEN GOTO 60 30 LET weeks = (days - 1) / 7 40 LET bps = 100 + 50 * weeks 50 RETURN bps 60 RETURN 100 End Function // ----------------------------------------------------------------------------- // ValidText(s, maxLen) : 1 when 1 <= STRLEN(s) <= maxLen, else 0. // Rejects empty and oversized questions / option labels. // ----------------------------------------------------------------------------- Function ValidText(s String, maxLen Uint64) Uint64 10 DIM n AS Uint64 20 LET n = STRLEN(s) 30 IF n >= 1 THEN GOTO 50 40 RETURN 0 50 IF n <= maxLen THEN GOTO 70 60 RETURN 0 70 RETURN 1 End Function // ----------------------------------------------------------------------------- // CreatePoll(requiredCollateral, votingDays, revealDays, question, // optionCount, o0..o19) // // The creation fee = requiredCollateral * FeeBasisPoints(votingDays+revealDays) // / 10000 and must arrive as DEROVALUE(). It is booked to fee_balance, is never // refunded, and is never taken from voter collateral. // // Option labels are validated and stored inline. Because a non-zero RETURN // discards every STORE made earlier in the same invocation, a rejection at // option 17 rolls the whole poll back — there is no partial poll. // ----------------------------------------------------------------------------- Function CreatePoll(requiredCollateral Uint64, votingDays Uint64, revealDays Uint64, question String, optionCount Uint64, o0 String, o1 String, o2 String, o3 String, o4 String, o5 String, o6 String, o7 String, o8 String, o9 String, o10 String, o11 String, o12 String, o13 String, o14 String, o15 String, o16 String, o17 String, o18 String, o19 String) Uint64 5 DIM pid, bps, fee AS Uint64 10 IF requiredCollateral >= 100 * 100000 THEN GOTO 30 20 RETURN 1 // collateral below 100 DERO 30 IF requiredCollateral <= 1000000 * 100000 THEN GOTO 50 40 RETURN 2 // collateral above 1,000,000 DERO 50 IF votingDays >= 1 THEN GOTO 70 60 RETURN 3 // voting period below 1 day 70 IF votingDays <= 365 THEN GOTO 90 80 RETURN 4 // voting period above 365 days 90 IF revealDays >= 1 THEN GOTO 110 100 RETURN 5 // reveal period below 1 day 110 IF revealDays <= 30 THEN GOTO 130 120 RETURN 6 // reveal period above 30 days 130 IF optionCount >= 2 THEN GOTO 150 140 RETURN 7 // need at least two options 150 IF optionCount <= 20 THEN GOTO 170 160 RETURN 8 // at most 20 options 170 IF ValidText(question, 200) == 1 THEN GOTO 190 180 RETURN 9 // question empty or over 200 chars 190 IF EXISTS("poll_count") THEN GOTO 210 200 STORE("poll_count", 0) 210 LET pid = LOAD("poll_count") + 1 220 STORE("poll_count", pid) // requiredCollateral <= 1e11 atoms and bps <= 3100 for the capped durations, // so the product is far below 2^64 and cannot wrap. 230 LET bps = FeeBasisPoints(votingDays + revealDays) 240 LET fee = requiredCollateral * bps / 10000 250 IF DEROVALUE() == fee THEN GOTO 270 260 RETURN 10 // wrong creation fee attached 270 IF EXISTS("fee_balance") THEN GOTO 290 280 STORE("fee_balance", 0) 290 STORE("fee_balance", LOAD("fee_balance") + fee) 300 STORE("p_question_" + pid, question) 310 STORE("p_creator_" + pid, SIGNER()) 320 STORE("p_fee_" + pid, fee) 330 STORE("p_collateral_" + pid, requiredCollateral) 340 STORE("p_option_count_" + pid, optionCount) 350 STORE("p_voting_days_" + pid, votingDays) 360 STORE("p_reveal_days_" + pid, revealDays) 370 STORE("p_start_" + pid, BLOCK_TIMESTAMP()) 380 STORE("p_vote_end_" + pid, BLOCK_TIMESTAMP() + votingDays * 86400) 390 STORE("p_reveal_end_" + pid, BLOCK_TIMESTAMP() + (votingDays + revealDays) * 86400) 400 STORE("p_phase_" + pid, 0) // 0 = VOTING 410 STORE("p_tokens_issued_" + pid, 0) 420 STORE("p_commits_" + pid, 0) 430 STORE("p_reveals_" + pid, 0) // --- option labels: validate then store, 0-based, inline dispatch --- // Only the first optionCount labels are read. Each is range-checked // before it is stored, and a non-zero RETURN discards the entire poll. 2000 IF optionCount <= 0 THEN GOTO 2900 // every label supplied and stored 2002 IF ValidText(o0, 100) == 1 THEN GOTO 2004 2003 RETURN 11 // option 0 empty or over 100 chars 2004 STORE("p_option_" + pid + "_0", o0) 2010 IF optionCount <= 1 THEN GOTO 2900 // every label supplied and stored 2012 IF ValidText(o1, 100) == 1 THEN GOTO 2014 2013 RETURN 12 // option 1 empty or over 100 chars 2014 STORE("p_option_" + pid + "_1", o1) 2020 IF optionCount <= 2 THEN GOTO 2900 // every label supplied and stored 2022 IF ValidText(o2, 100) == 1 THEN GOTO 2024 2023 RETURN 13 // option 2 empty or over 100 chars 2024 STORE("p_option_" + pid + "_2", o2) 2030 IF optionCount <= 3 THEN GOTO 2900 // every label supplied and stored 2032 IF ValidText(o3, 100) == 1 THEN GOTO 2034 2033 RETURN 14 // option 3 empty or over 100 chars 2034 STORE("p_option_" + pid + "_3", o3) 2040 IF optionCount <= 4 THEN GOTO 2900 // every label supplied and stored 2042 IF ValidText(o4, 100) == 1 THEN GOTO 2044 2043 RETURN 15 // option 4 empty or over 100 chars 2044 STORE("p_option_" + pid + "_4", o4) 2050 IF optionCount <= 5 THEN GOTO 2900 // every label supplied and stored 2052 IF ValidText(o5, 100) == 1 THEN GOTO 2054 2053 RETURN 16 // option 5 empty or over 100 chars 2054 STORE("p_option_" + pid + "_5", o5) 2060 IF optionCount <= 6 THEN GOTO 2900 // every label supplied and stored 2062 IF ValidText(o6, 100) == 1 THEN GOTO 2064 2063 RETURN 17 // option 6 empty or over 100 chars 2064 STORE("p_option_" + pid + "_6", o6) 2070 IF optionCount <= 7 THEN GOTO 2900 // every label supplied and stored 2072 IF ValidText(o7, 100) == 1 THEN GOTO 2074 2073 RETURN 18 // option 7 empty or over 100 chars 2074 STORE("p_option_" + pid + "_7", o7) 2080 IF optionCount <= 8 THEN GOTO 2900 // every label supplied and stored 2082 IF ValidText(o8, 100) == 1 THEN GOTO 2084 2083 RETURN 19 // option 8 empty or over 100 chars 2084 STORE("p_option_" + pid + "_8", o8) 2090 IF optionCount <= 9 THEN GOTO 2900 // every label supplied and stored 2092 IF ValidText(o9, 100) == 1 THEN GOTO 2094 2093 RETURN 20 // option 9 empty or over 100 chars 2094 STORE("p_option_" + pid + "_9", o9) 2100 IF optionCount <= 10 THEN GOTO 2900 // every label supplied and stored 2102 IF ValidText(o10, 100) == 1 THEN GOTO 2104 2103 RETURN 21 // option 10 empty or over 100 chars 2104 STORE("p_option_" + pid + "_10", o10) 2110 IF optionCount <= 11 THEN GOTO 2900 // every label supplied and stored 2112 IF ValidText(o11, 100) == 1 THEN GOTO 2114 2113 RETURN 22 // option 11 empty or over 100 chars 2114 STORE("p_option_" + pid + "_11", o11) 2120 IF optionCount <= 12 THEN GOTO 2900 // every label supplied and stored 2122 IF ValidText(o12, 100) == 1 THEN GOTO 2124 2123 RETURN 23 // option 12 empty or over 100 chars 2124 STORE("p_option_" + pid + "_12", o12) 2130 IF optionCount <= 13 THEN GOTO 2900 // every label supplied and stored 2132 IF ValidText(o13, 100) == 1 THEN GOTO 2134 2133 RETURN 24 // option 13 empty or over 100 chars 2134 STORE("p_option_" + pid + "_13", o13) 2140 IF optionCount <= 14 THEN GOTO 2900 // every label supplied and stored 2142 IF ValidText(o14, 100) == 1 THEN GOTO 2144 2143 RETURN 25 // option 14 empty or over 100 chars 2144 STORE("p_option_" + pid + "_14", o14) 2150 IF optionCount <= 15 THEN GOTO 2900 // every label supplied and stored 2152 IF ValidText(o15, 100) == 1 THEN GOTO 2154 2153 RETURN 26 // option 15 empty or over 100 chars 2154 STORE("p_option_" + pid + "_15", o15) 2160 IF optionCount <= 16 THEN GOTO 2900 // every label supplied and stored 2162 IF ValidText(o16, 100) == 1 THEN GOTO 2164 2163 RETURN 27 // option 16 empty or over 100 chars 2164 STORE("p_option_" + pid + "_16", o16) 2170 IF optionCount <= 17 THEN GOTO 2900 // every label supplied and stored 2172 IF ValidText(o17, 100) == 1 THEN GOTO 2174 2173 RETURN 28 // option 17 empty or over 100 chars 2174 STORE("p_option_" + pid + "_17", o17) 2180 IF optionCount <= 18 THEN GOTO 2900 // every label supplied and stored 2182 IF ValidText(o18, 100) == 1 THEN GOTO 2184 2183 RETURN 29 // option 18 empty or over 100 chars 2184 STORE("p_option_" + pid + "_18", o18) 2190 IF optionCount <= 19 THEN GOTO 2900 // every label supplied and stored 2192 IF ValidText(o19, 100) == 1 THEN GOTO 2194 2193 RETURN 30 // option 19 empty or over 100 chars 2194 STORE("p_option_" + pid + "_19", o19) 2900 RETURN 0 End Function // ----------------------------------------------------------------------------- // EnterPoll(pollID) : locks exactly requiredCollateral and grants ONE // poll-scoped Poll Voting Token in state 1 (ACTIVE). // Re-entry by the same wallet for the same poll is rejected, which is the // contract-level one-wallet-one-token enforcement. // Token states: 0 absent, 1 ACTIVE, 2 COMMITTED, 3 USED. // ----------------------------------------------------------------------------- Function EnterPoll(pollID Uint64) Uint64 10 IF pollID >= 1 THEN GOTO 30 20 RETURN 40 // bad poll id 30 IF EXISTS("poll_count") THEN GOTO 50 40 RETURN 41 // no polls exist 50 IF pollID <= LOAD("poll_count") THEN GOTO 70 60 RETURN 42 // poll does not exist 70 IF LOAD("p_phase_" + pollID) == 0 THEN GOTO 90 80 RETURN 43 // not in the voting phase 90 IF BLOCK_TIMESTAMP() < LOAD("p_vote_end_" + pollID) THEN GOTO 110 100 RETURN 44 // voting period has ended 110 IF EXISTS("p_token_" + pollID + SIGNER()) THEN GOTO 130 120 GOTO 150 // not yet entered: good 130 RETURN 45 // wallet already holds a token for this poll 150 IF DEROVALUE() == LOAD("p_collateral_" + pollID) THEN GOTO 170 160 RETURN 46 // wrong collateral amount 170 STORE("p_token_" + pollID + SIGNER(), 1) 180 STORE("p_collateral_" + pollID + SIGNER(), DEROVALUE()) 190 STORE("p_refunded_" + pollID + SIGNER(), 0) 200 STORE("p_revealed_" + pollID + SIGNER(), 0) 210 STORE("p_tokens_issued_" + pollID, LOAD("p_tokens_issued_" + pollID) + 1) 220 RETURN 0 End Function // ----------------------------------------------------------------------------- // CommitVote(pollID, commitment) // commitment : the 64-char lowercase hex digest the voter computed OFF-CHAIN // as HEX(SHA256("DEROVOTE_V2_COMMIT|" + pollID + "|" + // optionIndex + "|" + secret)). // The option index and the secret are deliberately NOT parameters. Passing // them would publish the ballot inside the transaction itself. // The token moves 1 ACTIVE -> 2 COMMITTED and never returns, so a committed // vote can never be replaced or edited. // ----------------------------------------------------------------------------- Function CommitVote(pollID Uint64, commitment String) Uint64 10 IF pollID >= 1 THEN GOTO 30 20 RETURN 50 // bad poll id 30 IF EXISTS("poll_count") THEN GOTO 50 40 RETURN 51 50 IF pollID <= LOAD("poll_count") THEN GOTO 70 60 RETURN 52 // poll does not exist 70 IF LOAD("p_phase_" + pollID) == 0 THEN GOTO 90 80 RETURN 53 // not in the voting phase 90 IF BLOCK_TIMESTAMP() < LOAD("p_vote_end_" + pollID) THEN GOTO 110 100 RETURN 54 // voting period has ended 110 IF EXISTS("p_token_" + pollID + SIGNER()) THEN GOTO 130 120 RETURN 55 // no token for this poll 130 IF LOAD("p_token_" + pollID + SIGNER()) == 1 THEN GOTO 150 140 RETURN 56 // token already committed or used 150 IF STRLEN(commitment) == 64 THEN GOTO 170 160 RETURN 57 // commitment must be 64 hex chars 170 IF EXISTS("p_commit_" + pollID + SIGNER()) THEN GOTO 190 180 STORE("p_commit_" + pollID + SIGNER(), commitment) 190 STORE("p_token_" + pollID + SIGNER(), 2) // 2 = COMMITTED, irreversible 200 STORE("p_commits_" + pollID, LOAD("p_commits_" + pollID) + 1) 210 RETURN 0 End Function // ----------------------------------------------------------------------------- // RevealVote(pollID, optionIndex, secret) // Recomputes HEX(SHA256("DEROVOTE_V2_COMMIT|" + pollID + "|" + optionIndex // + "|" + secret)) and compares it with the stored commitment. Only an exact // match tallies the option. Every rejection path returns non-zero, which // discards the whole invocation, so a bad reveal cannot move any tally. // The token moves 2 COMMITTED -> 3 USED on success and can never be replayed. // ----------------------------------------------------------------------------- Function RevealVote(pollID Uint64, optionIndex Uint64, secret String) Uint64 5 DIM expected AS String 10 IF pollID >= 1 THEN GOTO 30 20 RETURN 60 // bad poll id 30 IF EXISTS("poll_count") THEN GOTO 50 40 RETURN 61 50 IF pollID <= LOAD("poll_count") THEN GOTO 70 60 RETURN 62 // poll does not exist 70 IF LOAD("p_phase_" + pollID) == 1 THEN GOTO 90 80 RETURN 63 // not in the reveal phase 90 IF BLOCK_TIMESTAMP() < LOAD("p_reveal_end_" + pollID) THEN GOTO 110 100 RETURN 64 // reveal period has ended 110 IF EXISTS("p_token_" + pollID + SIGNER()) THEN GOTO 130 120 RETURN 65 // no token for this poll 130 IF LOAD("p_token_" + pollID + SIGNER()) == 2 THEN GOTO 150 140 RETURN 66 // token not committed, or already used 150 IF EXISTS("p_commit_" + pollID + SIGNER()) THEN GOTO 180 160 RETURN 67 // no commitment on record 180 IF EXISTS("p_revealed_" + pollID + SIGNER()) THEN GOTO 200 190 GOTO 240 // no flag stored: not revealed yet 200 IF LOAD("p_revealed_" + pollID + SIGNER()) == 1 THEN GOTO 220 210 GOTO 240 // flag present and zero: not revealed yet 220 RETURN 68 // already revealed 240 IF optionIndex < LOAD("p_option_count_" + pollID) THEN GOTO 260 250 RETURN 69 // option index out of range 260 IF STRLEN(secret) >= 1 THEN GOTO 280 270 RETURN 70 // empty secret 280 IF STRLEN(secret) <= 256 THEN GOTO 300 290 RETURN 71 // secret too long 300 LET expected = HEX(SHA256("DEROVOTE_V2_COMMIT|" + pollID + "|" + optionIndex + "|" + secret)) 310 IF expected == LOAD("p_commit_" + pollID + SIGNER()) THEN GOTO 330 320 RETURN 72 // digest mismatch: wrong option or secret 330 IF EXISTS("p_tally_" + pollID + "_" + optionIndex) THEN GOTO 350 340 STORE("p_tally_" + pollID + "_" + optionIndex, 0) 350 STORE("p_tally_" + pollID + "_" + optionIndex, LOAD("p_tally_" + pollID + "_" + optionIndex) + 1) 360 STORE("p_reveals_" + pollID, LOAD("p_reveals_" + pollID) + 1) 370 STORE("p_revealed_" + pollID + SIGNER(), 1) 380 STORE("p_token_" + pollID + SIGNER(), 3) // 3 = USED, permanently 390 RETURN 0 End Function // ----------------------------------------------------------------------------- // AdvancePoll(pollID) : permissionless phase advance, so a poll can never be // held hostage by a creator who disappears. Idempotent. // phase 0 -> 1 once BLOCK_TIMESTAMP >= p_vote_end // phase 1 -> 2 once BLOCK_TIMESTAMP >= p_reveal_end // Refund does NOT depend on this being called: it checks the on-chain deadline // itself and finalizes lazily, so a stuck phase can never trap collateral. // ----------------------------------------------------------------------------- Function AdvancePoll(pollID Uint64) Uint64 10 IF pollID >= 1 THEN GOTO 30 20 RETURN 80 30 IF EXISTS("poll_count") THEN GOTO 50 40 RETURN 81 50 IF pollID <= LOAD("poll_count") THEN GOTO 70 60 RETURN 82 // poll does not exist 70 IF BLOCK_TIMESTAMP() < LOAD("p_vote_end_" + pollID) THEN GOTO 110 80 IF LOAD("p_phase_" + pollID) == 0 THEN GOTO 90 85 GOTO 110 90 STORE("p_phase_" + pollID, 1) // -> REVEALING 100 GOTO 110 110 IF BLOCK_TIMESTAMP() < LOAD("p_reveal_end_" + pollID) THEN GOTO 160 120 IF LOAD("p_phase_" + pollID) == 1 THEN GOTO 140 130 RETURN 0 // already finalized 140 STORE("p_phase_" + pollID, 2) // -> FINALIZED 150 STORE("p_finalized_at_" + pollID, BLOCK_TIMESTAMP()) 160 RETURN 0 End Function // ----------------------------------------------------------------------------- // Refund(pollID) : returns the caller's FULL locked collateral for this poll. // The contract is the ONLY authority on eligibility: // - the reveal window must have closed on-chain (a timestamp comparison, not // a client-side countdown) // - the caller must have locked collateral on THIS poll // - it must not already have been refunded // The refund flag is set in the same atomic transaction as the transfer, and // because a non-zero RETURN discards everything, the flag can never be set // without the value moving. Double refund is therefore impossible. // A voter can only ever refund their OWN collateral: the amount is read from // the SIGNER()-suffixed record, so no caller can name another voter's row. // ----------------------------------------------------------------------------- Function Refund(pollID Uint64) Uint64 5 DIM amount AS Uint64 10 IF pollID >= 1 THEN GOTO 30 20 RETURN 90 30 IF EXISTS("poll_count") THEN GOTO 50 40 RETURN 91 50 IF pollID <= LOAD("poll_count") THEN GOTO 70 60 RETURN 92 // poll does not exist 70 IF BLOCK_TIMESTAMP() >= LOAD("p_reveal_end_" + pollID) THEN GOTO 90 80 RETURN 93 // refund not available yet 90 IF LOAD("p_phase_" + pollID) == 2 THEN GOTO 110 100 STORE("p_phase_" + pollID, 2) // finalize lazily 110 IF EXISTS("p_finalized_at_" + pollID) THEN GOTO 130 120 STORE("p_finalized_at_" + pollID, BLOCK_TIMESTAMP()) 130 IF EXISTS("p_collateral_" + pollID + SIGNER()) THEN GOTO 160 140 GOTO 150 150 RETURN 94 // wallet never locked collateral here 160 IF EXISTS("p_refunded_" + pollID + SIGNER()) THEN GOTO 190 170 GOTO 220 // no flag stored: not refunded yet 190 IF LOAD("p_refunded_" + pollID + SIGNER()) == 0 THEN GOTO 220 200 RETURN 95 // already refunded 210 GOTO 220 220 LET amount = LOAD("p_collateral_" + pollID + SIGNER()) 230 IF amount > 0 THEN GOTO 250 240 RETURN 96 // nothing locked 250 STORE("p_refunded_" + pollID + SIGNER(), 1) // mark first, atomically 260 SEND_DERO_TO_ADDRESS(SIGNER(), amount) 270 RETURN 0 End Function // ----------------------------------------------------------------------------- // WithdrawFees : project owner sweeps accumulated creation fees. This is the // ONLY path by which fee money moves and it is fully separate from the refund // path, so voter collateral and project fees can never mix. // ----------------------------------------------------------------------------- Function WithdrawFees(amount Uint64) Uint64 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 20 RETURN 100 // not the project owner 30 IF EXISTS("fee_balance") THEN GOTO 50 40 STORE("fee_balance", 0) 50 IF amount <= LOAD("fee_balance") THEN GOTO 70 60 RETURN 101 // exceeds collected fees 70 STORE("fee_balance", LOAD("fee_balance") - amount) 80 SEND_DERO_TO_ADDRESS(SIGNER(), amount) 90 RETURN 0 End Function // ----------------------------------------------------------------------------- // Two-step ownership handover, matching the reference DERO contracts. // ----------------------------------------------------------------------------- Function TransferOwnership(newowner String) Uint64 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 20 RETURN 110 30 STORE("tmpowner", ADDRESS_RAW(newowner)) 40 RETURN 0 End Function Function ClaimOwnership() Uint64 10 IF EXISTS("tmpowner") THEN GOTO 30 20 RETURN 111 30 IF LOAD("tmpowner") == SIGNER() THEN GOTO 50 40 RETURN 112 50 STORE("owner", SIGNER()) 60 STORE("tmpowner", "") 70 RETURN 0 End Function // ----------------------------------------------------------------------------- // Read helpers. DVM-BASIC has no read-only views, so these report state via the // return code. They mutate nothing. // ----------------------------------------------------------------------------- Function GetPhase(pollID Uint64) Uint64 10 IF EXISTS("p_phase_" + pollID) THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("p_phase_" + pollID) End Function Function GetTokenState(pollID Uint64) Uint64 10 IF EXISTS("p_token_" + pollID + SIGNER()) THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("p_token_" + pollID + SIGNER()) End Function Function GetTally(pollID Uint64, optionIndex Uint64) Uint64 10 IF EXISTS("p_tally_" + pollID + "_" + optionIndex) THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("p_tally_" + pollID + "_" + optionIndex) End Function Function GetRevealCount(pollID Uint64) Uint64 10 IF EXISTS("p_reveals_" + pollID) THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("p_reveals_" + pollID) End Function Function GetCommitCount(pollID Uint64) Uint64 10 IF EXISTS("p_commits_" + pollID) THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("p_commits_" + pollID) End Function Function GetTokenCount(pollID Uint64) Uint64 10 IF EXISTS("p_tokens_issued_" + pollID) THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("p_tokens_issued_" + pollID) End Function Function GetFeeBalance() Uint64 10 IF EXISTS("fee_balance") THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("fee_balance") End Function Function GetPollCount() Uint64 10 IF EXISTS("poll_count") THEN GOTO 30 20 RETURN 0 30 RETURN LOAD("poll_count") End Function " |
Prove Transaction
Prove that you sent DERO in this transaction. Obtain proof using the dero-wallet-cli command or from your wallet statement.
Note: The proof is sent to the server for verification calculations.
Payload proofs are user-provided display proofs; for the strongest verification, confirm with the receiving wallet.