CTOVoteTemplate
Voting logic for a single Community Takeover. Cloned per vote by the hook or graduation manager, initialized atomically in the same transaction.
Minimal proxy (EIP-1167). One instance per vote attempt. Holds voters' deposited tokens until voting closes. No owner.
Voting
vote(amount: uint256, support: bool) externalCast a vote by depositing tokens. Requires prior approve() to this contract. One position per address — you cannot vote twice or on both sides.
"Not initialized""Voting ended"/"Vote period expired""Amount must be > 0""Already voted"— useadd_votes"Transfer failed"
VoteCast(voter, amount, support)
add_votes(amount: uint256) externalAdd tokens to an existing position, in the same direction as your original vote. Does not increment the distinct-voter count.
"Must vote first", plus the same state checks asvote
Vote Lifecycle
end_vote() externalClose voting after the full duration. Anyone can call. Computes the outcome once and stores it in vote_passed; also records voting_ended_block.
The quorum denominator is read live during voting — buying to win is legitimate — but once closed the result must not be re-litigated by later balance changes, so it is frozen here.
"Not initialized","Already ended","Vote still active"
VoteEnded(yes_votes, no_votes, passed)
end_vote_early() externalClose voting before the full duration when quorum and majority already hold. Anyone can call. Available only after 3,600 s (1 hour) of the vote being open.
"Too early to end early""Quorum not met""Majority not achieved"
finalize() externalExecute a passed vote — calls back into the hook to transfer the deployer role to the initiator. Anyone can call. Reads the stored outcome rather than re-checking live state.
"Vote not ended""Already finalized""Vote did not pass"
VoteFinalized(new_deployer)
withdraw_tokens() externalReclaim deposited tokens after voting closes, regardless of outcome. Locked until a block strictly after voting_ended_block, so a flash-funded buy → vote → end → withdraw → repay cannot complete in one transaction.
"Vote not ended""Withdraw next block""No tokens to withdraw"
TokensWithdrawn(voter, amount)
Initialization
initialize(_token: address, _hook_address: address, _initiator: address) externalHook only — the hook clones and initializes atomically. Reads the stake already transferred in and registers it as the initiator's YES vote, making them voter number one.
"Already initialized""Only hook""Invalid token"/"Invalid hook"/"Invalid initiator""Initiator needs 5% of supply"
CTOInitialized(token, initiator, initiator_stake) and VoteCast(initiator, stake, True)
View Functions
| Function | Returns |
|---|---|
is_takeover_successful() -> bool | Live standing while open; the frozen outcome once closed |
get_vote_stats() | (yes, no, total, circulating, passing) |
get_circulating_supply() -> uint256 | Total supply minus LP-custodied minus burned |
get_yes_quorum_percentage() -> uint256 | YES as a percentage of circulating — the quorum metric |
get_participation_percentage() -> uint256 | All votes as a percentage of circulating |
get_yes_percentage() -> uint256 | YES as a percentage of votes cast |
get_voter_info(voter) | (balance, choice, has_voted) |
time_remaining() -> uint256 | Seconds until vote_end, or 0 |
voter_count() -> uint256 | Distinct accounts that have voted |
yes_votes() / no_votes() | Weighted tallies |
voting_ended() / vote_passed() / vote_finalized() | Lifecycle flags |
voting_ended_block() -> uint256 | Block voting closed — the withdrawal gate |
quorum_percentage() / vote_duration() | Constants |
yes_votes and no_votes are weighted — the incumbent deployer's stake counts 2×. voter_balances always stores the real deposited amount, so withdrawals are unaffected. Do not use the tallies to compute what a voter can withdraw.
Constants
| Constant | Value |
|---|---|
QUORUM_PERCENTAGE | 40% YES of circulating |
MIN_VOTERS | 10 |
VOTE_DURATION | 259,200 s (3 days) |
MIN_EARLY_END_DELAY | 3,600 s (1 hour) |
DEPLOYER_VOTE_MULTIPLIER | 2× |
| Majority rule | YES ≥ 1.1 × NO |
TOTAL_SUPPLY | 1,200,000,000 × 10⁹ (1.2B) |
INITIATOR_THRESHOLD | 60,000,000 × 10⁹ (60M, 5% of supply) |
LP_CUSTODIAN | Per chain — the contract that custodies pooled tokens. Resolved via the V3 factory on Gnosis. |