diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 75c714e86..f8c64e604 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -20,7 +20,10 @@ ** xref:finance.adoc[Finance] *** xref:/api/finance.adoc[API Reference] -** xref:governance.adoc[Governance] +** Governance +*** xref:/governance/governor.adoc[Governor] +*** xref:/governance/timelock.adoc[Timelock Controller] +*** xref:/governance/votes.adoc[Votes] *** xref:/api/governance.adoc[API Reference] ** xref:introspection.adoc[Introspection] @@ -33,7 +36,7 @@ *** xref:/api/security.adoc[API Reference] ** Tokens -*** xref:erc20.adoc[ERC20] +*** xref:/erc20.adoc[ERC20] **** xref:/guides/erc20-supply.adoc[Creating Supply] **** xref:/guides/erc20-permit.adoc[ERC20Permit] **** xref:/api/erc20.adoc[API Reference] diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index d137d30db..cc0b2881d 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -22,6 +22,7 @@ TIP: For an overview of ERC721, read our xref:erc721.adoc[ERC721 guide]. ```cairo use openzeppelin_token::erc721::interface::IERC721; ``` + Interface of the IERC721 standard as defined in {eip721}. [.contract-index] diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index 3a191df5d..87f8151e4 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -10,11 +10,1977 @@ :VotingUnitsTrait: xref:VotingUnitsTrait[VotingUnitsTrait] :VotesComponent: xref:VotesComponent[VotesComponent] :IVotes: xref:IVotes[IVotes] +:governor: xref:governance/governor.adoc[Governor] +:inner-src5: xref:api/introspection.adoc#ISRC5[SRC5 ID] +:GovernorComponent: xref:#GovernorComponent[GovernorComponent] +:TimelockControllerComponent: xref:#TimelockControllerComponent[TimelockControllerComponent] +:ERC-6372: https://eips.ethereum.org/EIPS/eip-6372[ERC-6372] +:SNIP-12: https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md[SNIP-12] = Governance +include::../utils/_common.adoc[] + This crate includes primitives for on-chain governance. +== Governor + +This modular system of Governor components allows the deployment of easily customizable on-chain voting protocols. + +TIP: For a walkthrough of how to implement a Governor, check the {governor} page. + +[.contract] +[[IGovernor]] +=== `++IGovernor++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/interface.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::interface::IGovernor; +``` + +Interface of a governor contract. + +[.contract-index] +.{inner-src5} +-- +0x1100a1f8546595b5bd75a6cd8fcc5b015370655e66f275963321c5cd0357ac9 +-- + +[.contract-index] +.Functions +-- +* xref:#IGovernor-name[`++name()++`] +* xref:#IGovernor-version[`++version()++`] +* xref:#IGovernor-COUNTING_MODE[`++COUNTING_MODE()++`] +* xref:#IGovernor-hash_proposal[`++hash_proposal(calls, description_hash)++`] +* xref:#IGovernor-state[`++state(proposal_id)++`] +* xref:#IGovernor-proposal_threshold[`++proposal_threshold()++`] +* xref:#IGovernor-proposal_snapshot[`++proposal_snapshot(proposal_id)++`] +* xref:#IGovernor-proposal_deadline[`++proposal_deadline(proposal_id)++`] +* xref:#IGovernor-proposal_proposer[`++proposal_proposer(proposal_id)++`] +* xref:#IGovernor-proposal_eta[`++proposal_eta(proposal_id)++`] +* xref:#IGovernor-proposal_needs_queuing[`++proposal_needs_queuing(proposal_id)++`] +* xref:#IGovernor-voting_delay[`++voting_delay()++`] +* xref:#IGovernor-voting_period[`++voting_period()++`] +* xref:#IGovernor-quorum[`++quorum(timepoint)++`] +* xref:#IGovernor-get_votes[`++get_votes(account, timepoint)++`] +* xref:#IGovernor-get_votes_with_params[`++get_votes_with_params(account, timepoint, params)++`] +* xref:#IGovernor-has_voted[`++has_voted(proposal_id, account)++`] +* xref:#IGovernor-propose[`++propose(calls, description)++`] +* xref:#IGovernor-queue[`++queue(calls, description_hash)++`] +* xref:#IGovernor-execute[`++execute(calls, description_hash)++`] +* xref:#IGovernor-cancel[`++cancel(proposal_id, description_hash)++`] +* xref:#IGovernor-cast_vote[`++cast_vote(proposal_id, support)++`] +* xref:#IGovernor-cast_vote_with_reason[`++cast_vote_with_reason(proposal_id, support, reason)++`] +* xref:#IGovernor-cast_vote_with_reason_and_params[`++cast_vote_with_reason_and_params(proposal_id, support, reason, params)++`] +* xref:#IGovernor-cast_vote_by_sig[`++cast_vote_by_sig(proposal_id, support, reason, signature)++`] +* xref:#IGovernor-cast_vote_with_reason_and_params_by_sig[`++cast_vote_with_reason_and_params_by_sig(proposal_id, support, reason, params, signature)++`] +* xref:#IGovernor-nonces[`++nonces(voter)++`] +* xref:#IGovernor-relay[`++relay(call)++`] +-- + +[.contract-index] +.Events +-- +* xref:#IGovernor-ProposalCreated[`++ProposalCreated(proposal_id, proposer, calls, signatures, vote_start, vote_end, description)++`] +* xref:#IGovernor-ProposalQueued[`++ProposalQueued(proposal_id, eta_seconds)++`] +* xref:#IGovernor-ProposalExecuted[`++ProposalExecuted(proposal_id)++`] +* xref:#IGovernor-ProposalCanceled[`++ProposalCanceled(proposal_id)++`] +* xref:#IGovernor-VoteCast[`++VoteCast(voter, proposal_id, support, weight, reason)++`] +* xref:#IGovernor-VoteCastWithParams[`++VoteCastWithParams(voter, proposal_id, support, weight, reason, params)++`] +-- + +[#IGovernor-Functions] +==== Functions + +[.contract-item] +[[IGovernor-name]] +==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external# + +Name of the governor instance (used in building the {SNIP-12} domain separator). + +[.contract-item] +[[IGovernor-version]] +==== `[.contract-item-name]#++version++#++() → felt252++` [.item-kind]#external# + +Version of the governor instance (used in building {SNIP-12} domain separator). + +[.contract-item] +[[IGovernor-COUNTING_MODE]] +==== `[.contract-item-name]#++COUNTING_MODE++#++() → ByteArray++` [.item-kind]#external# + +A description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs +to show correct vote options and interpret the results. The string is a URL-encoded sequence of key-value pairs +that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in +`GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` +key with a unique name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between +for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +NOTE: The string can be decoded by the standard https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`] +JavaScript class. + +[.contract-item] +[[IGovernor-hash_proposal]] +==== `[.contract-item-name]#++hash_proposal++#++(calls: Span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Hashing function used to (re)build the proposal id from the proposal details. + +[.contract-item] +[[IGovernor-state]] +==== `[.contract-item-name]#++state++#++(proposal_id: felt252) → ProposalState++` [.item-kind]#external# + +Returns the state of a proposal, given its id. + +[.contract-item] +[[IGovernor-proposal_threshold]] +==== `[.contract-item-name]#++proposal_threshold++#++() → u256++` [.item-kind]#external# + +The number of votes required in order for a voter to become a proposer. + +[.contract-item] +[[IGovernor-proposal_snapshot]] +==== `[.contract-item-name]#++proposal_snapshot++#++(proposal_id: felt252) → u64++` [.item-kind]#external# + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the +end of this block. Hence, voting for this proposal starts at the beginning of the following block. + +[.contract-item] +[[IGovernor-proposal_deadline]] +==== `[.contract-item-name]#++proposal_deadline++#++(proposal_id: felt252) → u64++` [.item-kind]#external# + +Timepoint at which votes close. If using block number, votes close at the end of this block, so +it is possible to cast a vote during this block. + +[.contract-item] +[[IGovernor-proposal_proposer]] +==== `[.contract-item-name]#++proposal_proposer++#++(proposal_id: felt252) → ContractAddress++` [.item-kind]#external# + +The account that created a proposal. + +[.contract-item] +[[IGovernor-proposal_eta]] +==== `[.contract-item-name]#++proposal_eta++#++(proposal_id: felt252) → u64++` [.item-kind]#external# + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and +`proposal_deadline`, this doesn't use the governor clock, and instead relies on the +executor's clock which may be different. In most cases this will be a timestamp. + +[.contract-item] +[[IGovernor-proposal_needs_queuing]] +==== `[.contract-item-name]#++proposal_needs_queuing++#++(proposal_id: felt252) → bool++` [.item-kind]#external# + +Whether a proposal needs to be queued before execution. This indicates if the proposal needs to go through a timelock. + +[.contract-item] +[[IGovernor-voting_delay]] +==== `[.contract-item-name]#++voting_delay++#++() → u64++` [.item-kind]#external# + +Delay between when a proposal is created and when the vote starts. The unit this duration is expressed in +depends on the clock (see {ERC-6372}) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + +[.contract-item] +[[IGovernor-voting_period]] +==== `[.contract-item-name]#++voting_period++#++() → u64++` [.item-kind]#external# + +Delay between when a vote starts and when it ends. The unit this duration is expressed in depends on +the clock (see {ERC-6372}) this contract uses. + +NOTE: The `voting_delay` can delay the start of the vote. This must be considered when +setting the voting duration compared to the voting delay. + +NOTE: This value is stored when the proposal is submitted so that possible changes to the +value do not affect proposals that have already been submitted. + +[.contract-item] +[[IGovernor-quorum]] +==== `[.contract-item-name]#++quorum++#++(timepoint: u64) → u256++` [.item-kind]#external# + +Minimum number of votes required for a proposal to be successful. + +NOTE: The `timepoint` parameter corresponds to the snapshot used for counting vote. This +allows the quorum to scale depending on values such as the total supply of a token at this +timepoint. + +[.contract-item] +[[IGovernor-get_votes]] +==== `[.contract-item-name]#++get_votes++#++(account: ContractAddress, timepoint: u64) → u256++` [.item-kind]#external# + +Returns the voting power of an `account` at a specific `timepoint`. + +NOTE: This can be implemented in a number of ways, for example by reading the delegated +balance from one (or multiple) `ERC20Votes` tokens. + +[.contract-item] +[[IGovernor-get_votes_with_params]] +==== `[.contract-item-name]#++get_votes_with_params++#++(account: ContractAddress, timepoint: u64, params: Span) → u256++` [.item-kind]#external# + +Returns the voting power of an `account` at a specific `timepoint`, given additional encoded parameters. + +[.contract-item] +[[IGovernor-has_voted]] +==== `[.contract-item-name]#++has_voted++#++(proposal_id: felt252, account: ContractAddress) → bool++` [.item-kind]#external# + +Returns whether an `account` has cast a vote on a proposal. + +[.contract-item] +[[IGovernor-propose]] +==== `[.contract-item-name]#++propose++#++(calls: Span, description: ByteArray) → felt252++` [.item-kind]#external# + +Creates a new proposal. Vote starts after a delay specified by `voting_delay` and lasts for a duration specified by `voting_period`. + +NOTE: The state of the Governor and targets may change between the proposal creation and its execution. +This may be the result of third party actions on the targeted contracts, or other governor proposals. +For example, the balance of this contract could be updated or its access control permissions may be +modified, possibly compromising the proposal's ability to execute successfully (e.g. the governor +doesn't have enough value to cover a proposal with multiple transfers). + +Returns the id of the proposal. + +[.contract-item] +[[IGovernor-queue]] +==== `[.contract-item-name]#++queue++#++(calls: Span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Queue a proposal. Some governors require this step to be performed before execution can +happen. If queuing is not necessary, this function may revert. + +Queuing a proposal requires the quorum to be reached, the vote to be successful, and the +deadline to be reached. + +Returns the id of the proposal. + +[.contract-item] +[[IGovernor-execute]] +==== `[.contract-item-name]#++execute++#++(calls: span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Execute a successful proposal. This requires the quorum to be reached, the vote to be +successful, and the deadline to be reached. Depending on the governor it might also be +required that the proposal was queued and that some delay passed. + +NOTE: Some modules can modify the requirements for execution, for example by adding an +additional timelock (See `timelock_controller`). + +Returns the id of the proposal. + +[.contract-item] +[[IGovernor-cancel]] +==== `[.contract-item-name]#++cancel++#++(calls: Span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending +state, i.e. before the vote starts. + +Returns the id of the proposal. + +[.contract-item] +[[IGovernor-cast_vote]] +==== `[.contract-item-name]#++cast_vote++#++(proposal_id: felt252, support: u8) → u256++` [.item-kind]#external# + +Cast a vote on a proposal. + +Returns the weight of the vote. + +[.contract-item] +[[IGovernor-cast_vote_with_reason]] +==== `[.contract-item-name]#++cast_vote_with_reason++#++(proposal_id: felt252, support: u8, reason: ByteArray) → u256++` [.item-kind]#external# + +Cast a vote on a proposal with a `reason`. + +Returns the weight of the vote. + +[.contract-item] +[[IGovernor-cast_vote_with_reason_and_params]] +==== `[.contract-item-name]#++cast_vote_with_reason_and_params++#++(proposal_id: felt252, support: u8, reason: ByteArray, params: Span) → u256++` [.item-kind]#external# + +Cast a vote on a proposal with a reason and additional encoded parameters. + +Returns the weight of the vote. + +[.contract-item] +[[IGovernor-cast_vote_by_sig]] +==== `[.contract-item-name]#++cast_vote_by_sig++#++(proposal_id: felt252, support: u8, voter: ContractAddress, signature: Span) → u256++` [.item-kind]#external# + +Cast a vote on a proposal using the voter's signature. + +Returns the weight of the vote. + +[.contract-item] +[[IGovernor-cast_vote_with_reason_and_params_by_sig]] +==== `[.contract-item-name]#++cast_vote_with_reason_and_params_by_sig++#++(proposal_id: felt252, support: u8, voter: ContractAddress, reason: ByteArray, params: Span, signature: Span) → u256++` [.item-kind]#external# + +Cast a vote on a proposal with a reason and additional encoded parameters using the voter's signature. + +Returns the weight of the vote. + +[.contract-item] +[[IGovernor-nonces]] +==== `[.contract-item-name]#++nonces++#++(voter: ContractAddress) → felt252++` [.item-kind]#external# + +Returns the next unused nonce for an address. + +[.contract-item] +[[IGovernor-relay]] +==== `[.contract-item-name]#++relay++#++(call: Call)++` [.item-kind]#external# + +Relays a transaction or function call to an arbitrary target. + +In cases where the governance executor is some contract other than the governor itself, like +when using a timelock, this function can be invoked in a governance proposal to recover +tokens that were sent to the governor contract by mistake. + +NOTE: If the executor is simply the governor itself, use of `relay` is redundant. + +[#IGovernor-Events] +==== Events + +[.contract-item] +[[IGovernor-ProposalCreated]] +==== `[.contract-item-name]#++ProposalCreated++#++(proposal_id: felt252, proposer: ContractAddress, calls: Span, signatures: Span>, vote_start: u64, vote_end: u64, description: ByteArray)++` [.item-kind]#event# + +Emitted when a proposal is created. + +[.contract-item] +[[IGovernor-ProposalQueued]] +==== `[.contract-item-name]#++ProposalQueued++#++(proposal_id: felt252, eta_seconds: u64)++` [.item-kind]#event# + +Emitted when a proposal is queued. + +[.contract-item] +[[IGovernor-ProposalExecuted]] +==== `[.contract-item-name]#++ProposalExecuted++#++(proposal_id: felt252)++` [.item-kind]#event# + +Emitted when a proposal is executed. + +[.contract-item] +[[IGovernor-ProposalCanceled]] +==== `[.contract-item-name]#++ProposalCanceled++#++(proposal_id: felt252)++` [.item-kind]#event# + +Emitted when a proposal is canceled. + +[.contract-item] +[[IGovernor-VoteCast]] +==== `[.contract-item-name]#++VoteCast++#++(voter: ContractAddress, proposal_id: felt252, support: u8, weight: u256, reason: ByteArray)++` [.item-kind]#event# + +Emitted when a vote is cast. + +[.contract-item] +[[IGovernor-VoteCastWithParams]] +==== `[.contract-item-name]#++VoteCastWithParams++#++(voter: ContractAddress, proposal_id: felt252, support: u8, weight: u256, reason: ByteArray, params: Span)++` [.item-kind]#event# + +Emitted when a vote is cast with params. + +:ProposalCreated: xref:GovernorComponent-ProposalCreated[ProposalCreated] +:ProposalExecuted: xref:GovernorComponent-ProposalExecuted[ProposalExecuted] +:ProposalQueued: xref:GovernorComponent-ProposalQueued[ProposalQueued] +:ProposalCanceled: xref:GovernorComponent-ProposalCanceled[ProposalCanceled] +:VoteCast: xref:GovernorComponent-VoteCast[VoteCast] +:VoteCastWithParams: xref:GovernorComponent-VoteCastWithParams[VoteCastWithParams] +:component-extensions: xref:#governor_extensions[component extensions] + +[.contract] +[[GovernorComponent]] +=== `++GovernorComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/governor.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::GovernorComponent; +``` + +Core of the governance system. + +NOTE: The extension traits presented below are what make the GovernorComponent a modular and configurable system. The embeddable +and internal implementations depends on these trait. They can be implemented locally in the contract, or through the provided library +{component-extensions}. + +NOTE: {src5-component-required-note} + +[.contract-index#GovernorComponent-Extensions-Traits-Traits] +.Extensions traits +-- +[.sub-index#GovernorComponent-Extensions-Traits-GovernorSettingsTrait] +.GovernorSettingsTrait + +* xref:#GovernorComponent-GovernorSettingsTrait-voting_delay[`++voting_delay(self)++`] +* xref:#GovernorComponent-GovernorSettingsTrait-voting_period[`++voting_period(self)++`] +* xref:#GovernorComponent-GovernorSettingsTrait-proposal_threshold[`++proposal_threshold(self)++`] + +[.sub-index#GovernorComponent-Extensions-Traits-GovernorQuorumTrait] +.GovernorQuorumTrait + +* xref:#GovernorComponent-GovernorQuorumTrait-quorum[`++quorum(self, timepoint)++`] + +[.sub-index#GovernorComponent-Extensions-Traits-GovernorCountingTrait] +.GovernorCountingTrait + +* xref:#GovernorComponent-GovernorCountingTrait-counting_mode[`++counting_mode(self)++`] +* xref:#GovernorComponent-GovernorCountingTrait-count_vote[`++count_vote(self, proposal_id, account, support, total_weight, params)++`] +* xref:#GovernorComponent-GovernorCountingTrait-has_voted[`++has_voted(self, proposal_id, account)++`] +* xref:#GovernorComponent-GovernorCountingTrait-quorum_reached[`++quorum_reached(self, proposal_id)++`] +* xref:#GovernorComponent-GovernorCountingTrait-vote_succeeded[`++vote_succeeded(self, proposal_id)++`] + +[.sub-index#GovernorComponent-Extensions-Traits-GovernorVotesTrait] +.GovernorVotesTrait + +* xref:#GovernorComponent-GovernorVotesTrait-clock[`++clock(self)++`] +* xref:#GovernorComponent-GovernorVotesTrait-clock_mode[`++clock_mode(self)++`] +* xref:#GovernorComponent-GovernorVotesTrait-get_votes[`++get_votes(self, account, timepoint, params)++`] + +[.sub-index#GovernorComponent-Extensions-Traits-GovernorExecutionTrait] +.GovernorExecutionTrait + +* xref:#GovernorComponent-GovernorExecutionTrait-state[`++state(self, proposal_id)++`] +* xref:#GovernorComponent-GovernorExecutionTrait-executor[`++executor(self)++`] +* xref:#GovernorComponent-GovernorExecutionTrait-execute_operations[`++execute_operations(self, proposal_id, calls, description_hash)++`] +* xref:#GovernorComponent-GovernorExecutionTrait-queue_operations[`++queue_operations(self, proposal_id, calls, description_hash)++`] +* xref:#GovernorComponent-GovernorExecutionTrait-proposal_needs_queuing[`++proposal_needs_queuing(self, proposal_id)++`] +* xref:#GovernorComponent-GovernorExecutionTrait-cancel_operations[`++cancel_operations(self, proposal_id, description_hash)++`] +-- + +[.contract-index#GovernorComponent-Embeddable-Impls] +.Embeddable Implementations +-- +[.sub-index#GovernorComponent-Embeddable-Impls-GovernorImpl] +.GovernorImpl + +* xref:#GovernorComponent-name[`++name(self)++`] +* xref:#GovernorComponent-version[`++version(self)++`] +* xref:#GovernorComponent-COUNTING_MODE[`++COUNTING_MODE(self)++`] +* xref:#GovernorComponent-hash_proposal[`++hash_proposal(self, calls, description_hash)++`] +* xref:#GovernorComponent-state[`++state(self, proposal_id)++`] +* xref:#GovernorComponent-proposal_threshold[`++proposal_threshold(self)++`] +* xref:#GovernorComponent-proposal_snapshot[`++proposal_snapshot(self, proposal_id)++`] +* xref:#GovernorComponent-proposal_deadline[`++proposal_deadline(self, proposal_id)++`] +* xref:#GovernorComponent-proposal_proposer[`++proposal_proposer(self, proposal_id)++`] +* xref:#GovernorComponent-proposal_eta[`++proposal_eta(self, proposal_id)++`] +* xref:#GovernorComponent-proposal_needs_queuing[`++proposal_needs_queuing(self, proposal_id)++`] +* xref:#GovernorComponent-voting_delay[`++voting_delay(self)++`] +* xref:#GovernorComponent-voting_period[`++voting_period(self)++`] +* xref:#GovernorComponent-quorum[`++quorum(self, timepoint)++`] +* xref:#GovernorComponent-get_votes[`++get_votes(self, account, timepoint)++`] +* xref:#GovernorComponent-get_votes_with_params[`++get_votes_with_params(self, account, timepoint, params)++`] +* xref:#GovernorComponent-has_voted[`++has_voted(self, proposal_id, account)++`] +* xref:#GovernorComponent-propose[`++propose(self, calls, description)++`] +* xref:#GovernorComponent-queue[`++queue(self, calls, description_hash)++`] +* xref:#GovernorComponent-execute[`++execute(self, calls, description_hash)++`] +* xref:#GovernorComponent-cancel[`++cancel(self, proposal_id, description_hash)++`] +* xref:#GovernorComponent-cast_vote[`++cast_vote(self, proposal_id, support)++`] +* xref:#GovernorComponent-cast_vote_with_reason[`++cast_vote_with_reason(self, proposal_id, support, reason)++`] +* xref:#GovernorComponent-cast_vote_with_reason_and_params[`++cast_vote_with_reason_and_params(self, proposal_id, support, reason, params)++`] +* xref:#GovernorComponent-cast_vote_by_sig[`++cast_vote_by_sig(self, proposal_id, support, reason, signature)++`] +* xref:#GovernorComponent-cast_vote_with_reason_and_params_by_sig[`++cast_vote_with_reason_and_params_by_sig(self, proposal_id, support, reason, params, signature)++`] +* xref:#GovernorComponent-nonces[`++nonces(self, voter)++`] +* xref:#GovernorComponent-relay[`++relay(self, call)++`] +-- + +[.contract-index] +.Internal Implementations +-- +.InternalImpl + +* xref:#GovernorComponent-initializer[`++initializer(self)++`] +* xref:#GovernorComponent-get_proposal[`++get_proposal(self, proposal_id)++`] +* xref:#GovernorComponent-is_valid_description_for_proposer[`++is_valid_description_for_proposer(self, proposer, description)++`] +* xref:#GovernorComponent-_hash_proposal[`++_hash_proposal(self, calls, description_hash)++`] +* xref:#GovernorComponent-_proposal_snapshot[`++_proposal_snapshot(self, proposal_id)++`] +* xref:#GovernorComponent-_proposal_deadline[`++_proposal_deadline(self, proposal_id)++`] +* xref:#GovernorComponent-_proposal_proposer[`++_proposal_proposer(self, proposal_id)++`] +* xref:#GovernorComponent-_proposal_eta[`++_proposal_eta(self, proposal_id)++`] + +.InternalExtendedImpl + +* xref:#GovernorComponent-assert_only_governance[`++assert_only_governance(self)++`] +* xref:#GovernorComponent-validate_state[`++validate_state(self, proposal_id, allowed_states)++`] +* xref:#GovernorComponent-use_nonce[`++use_nonce(self, voter)++`] +* xref:#GovernorComponent-_get_votes[`++_get_votes(self, account, timepoint, params)++`] +* xref:#GovernorComponent-_proposal_threshold[`++_proposal_threshold(self)++`] +* xref:#GovernorComponent-_state[`++_state(self, proposal_id)++`] +* xref:#GovernorComponent-_propose[`++_propose(self, calls, description, proposer)++`] +* xref:#GovernorComponent-_cancel[`++_cancel(self, proposal_id, description_hash)++`] +* xref:#GovernorComponent-_count_vote[`++_count_vote(self, proposal_id, account, support, total_weight, params)++`] +* xref:#GovernorComponent-_cast_vote[`++_cast_vote(self, proposal_id, voter, support, reason, params)++`] +-- + +[.contract-index] +.Events +-- +* xref:#GovernorComponent-ProposalCreated[`++ProposalCreated(proposal_id, proposer, calls, signatures, vote_start, vote_end, description)++`] +* xref:#GovernorComponent-ProposalQueued[`++ProposalQueued(proposal_id)++`] +* xref:#GovernorComponent-ProposalExecuted[`++ProposalExecuted(proposal_id)++`] +* xref:#GovernorComponent-ProposalCanceled[`++ProposalCanceled(proposal_id)++`] +* xref:#GovernorComponent-VoteCast[`++VoteCast(voter, proposal_id, support, weight, reason)++`] +* xref:#GovernorComponent-VoteCastWithParams[`++VoteCastWithParams(voter, proposal_id, support, weight, reason, params)++`] +-- + +[#GovernorComponent-Extensions-Traits] +==== Extensions traits functions + +[.contract-item] +[[GovernorComponent-GovernorSettingsTrait-voting_delay]] +==== `[.contract-item-name]#++voting_delay++#++(self: @ContractState) → u64++` [.item-kind]#extension# + +Must return the delay, in number of timepoints, between when the proposal is created and when the vote starts. This can be +increased to leave time for users to buy voting power, or delegate it, before the voting of a +proposal starts. + +[.contract-item] +[[GovernorComponent-GovernorSettingsTrait-voting_period]] +==== `[.contract-item-name]#++voting_period++#++(self: @ContractState) → u64++` [.item-kind]#extension# + +Must return the delay, in number of timepoints, between the vote start and vote end. + +[.contract-item] +[[GovernorComponent-GovernorSettingsTrait-proposal_threshold]] +==== `[.contract-item-name]#++proposal_threshold++#++(self: @ContractState) → u256++` [.item-kind]#extension# + +Must return the minimum number of votes that an account must have to create a proposal. + +[.contract-item] +[[GovernorComponent-GovernorQuorumTrait-quorum]] +==== `[.contract-item-name]#++quorum++#++(self: @ContractState, timepoint: u64) → u256++` [.item-kind]#extension# + +Must return the minimum number of votes required for a proposal to succeed. + +[.contract-item] +[[GovernorComponent-GovernorCountingTrait-counting_mode]] +==== `[.contract-item-name]#++counting_mode++#++(self: @ContractState) → ByteArray++` [.item-kind]#extension# + +Must return a description of the possible `support` values for `cast_vote` and the way these votes are counted, +meant to be consumed by UIs to show correct vote options and interpret the results. +See <> for more details. + +[.contract-item] +[[GovernorComponent-GovernorCountingTrait-count_vote]] +==== `[.contract-item-name]#++count_vote++#++(ref self: ContractState, proposal_id: felt252, account: ContractAddress, support: u8, total_weight: u256, params: Span) → u256++` [.item-kind]#extension# + +Must register a vote for `proposal_id` by `account` with a given `support`, voting `weight` and voting `params`. + +NOTE: Support is generic and can represent various things depending on the voting system used. + +[.contract-item] +[[GovernorComponent-GovernorCountingTrait-has_voted]] +==== `[.contract-item-name]#++has_voted++#++(self: @ContractState, proposal_id: felt252, account: ContractAddress) → bool++` [.item-kind]#extension# + +Must return whether an account has cast a vote on a proposal. + +[.contract-item] +[[GovernorComponent-GovernorCountingTrait-quorum_reached]] +==== `[.contract-item-name]#++quorum_reached++#++(self: @ContractState, proposal_id: felt252) → bool++` [.item-kind]#extension# + +Must return whether the minimum quorum has been reached for a proposal. + +[.contract-item] +[[GovernorComponent-GovernorCountingTrait-vote_succeeded]] +==== `[.contract-item-name]#++vote_succeeded++#++(self: @ContractState, proposal_id: felt252) → bool++` [.item-kind]#extension# + +Must return whether a proposal has succeeded or not. + +[.contract-item] +[[GovernorComponent-GovernorVotesTrait-clock]] +==== `[.contract-item-name]#++clock++#++(self: @ContractState) → u64++` [.item-kind]#extension# + +Must return the current timepoint according to the clock mode the governor is operating in. + +NOTE: For now, only timestamp is supported. + +[.contract-item] +[[GovernorComponent-GovernorVotesTrait-clock_mode]] +==== `[.contract-item-name]#++clock_mode++#++(self: @ContractState) → ByteArray++` [.item-kind]#extension# + +Must return the clock mode the governor is operating in. + +NOTE: For now, only timestamp is supported. + +[.contract-item] +[[GovernorComponent-GovernorVotesTrait-get_votes]] +==== `[.contract-item-name]#++get_votes++#++(self: @ContractState, account: ContractAddress, timepoint: u64, params: Span) → u256++` [.item-kind]#extension# + +Must return the voting power of an account at a specific timepoint with the given parameters. + +[.contract-item] +[[GovernorComponent-GovernorExecutionTrait-state]] +==== `[.contract-item-name]#++state++#++(self: @ContractState, proposal_id: felt252) → ProposalState++` [.item-kind]#extension# + +Must return the state of a proposal at the current time. + +The state can be either: + +- `Pending`: The proposal does not exist yet. +- `Active`: The proposal is active. +- `Canceled`: The proposal has been canceled. +- `Defeated`: The proposal has been defeated. +- `Succeeded`: The proposal has succeeded. +- `Queued`: The proposal has been queued. +- `Executed`: The proposal has been executed. + +[.contract-item] +[[GovernorComponent-GovernorExecutionTrait-executor]] +==== `[.contract-item-name]#++executor++#++(self: @ContractState) → ContractAddress++` [.item-kind]#internal# + +Must return the address through which the governor executes action. +Should be used to specify whether the module execute actions through another contract +such as a timelock. + +NOTE: MUST be the governor itself, or an instance of TimelockController with the +governor as the only proposer, canceller, and executor. + +WARNING: When the executor is not the governor itself (i.e. a timelock), it can call +functions that are restricted with the `assert_only_governance` guard, and also +potentially execute transactions on behalf of the governor. Because of this, this module +is designed to work with the TimelockController as the unique potential external +executor. + +[.contract-item] +[[GovernorComponent-GovernorExecutionTrait-execute_operations]] +==== `[.contract-item-name]#++execute_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span)++` [.item-kind]#internal# + +Execution mechanism. Can be used to modify the way operations are executed (for example adding a vault/timelock). + +[.contract-item] +[[GovernorComponent-GovernorExecutionTrait-queue_operations]] +==== `[.contract-item-name]#++queue_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span)++` [.item-kind]#internal# + +Queuing mechanism. Can be used to modify the way queuing is +performed (for example adding a vault/timelock). + +Requirements: + +- Must return a timestamp that describes the expected ETA for execution. If the returned +value is 0, the core will consider queueing did not succeed, and the public `queue` +function will revert. + +[.contract-item] +[[GovernorComponent-GovernorExecutionTrait-proposal_needs_queuing]] +==== `[.contract-item-name]#++proposal_needs_queuing++#++(self: @ContractState) → bool++` [.item-kind]#internal# + +Must return whether proposals need to be queued before execution. This usually indicates if the proposal needs to go through a timelock. + +[.contract-item] +[[GovernorComponent-GovernorExecutionTrait-cancel_operations]] +==== `[.contract-item-name]#++cancel_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span)++` [.item-kind]#internal# + +Cancel mechanism. Can be used to modify the way canceling is +performed (for example adding a vault/timelock). + +[#GovernorComponent-Embeddable-Functions] +==== Embeddable functions + +[.contract-item] +[[GovernorComponent-name]] +==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external# + +Name of the governor instance (used in building the {SNIP-12} domain separator). + +[.contract-item] +[[GovernorComponent-version]] +==== `[.contract-item-name]#++version++#++() → felt252++` [.item-kind]#external# + +Version of the governor instance (used in building {SNIP-12} domain separator). + +[.contract-item] +[[GovernorComponent-COUNTING_MODE]] +==== `[.contract-item-name]#++COUNTING_MODE++#++() → ByteArray++` [.item-kind]#external# + +A description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs +to show correct vote options and interpret the results. The string is a URL-encoded sequence of key-value pairs +that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in +`GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` +key with a unique name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between +for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +NOTE: The string can be decoded by the standard https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`] +JavaScript class. + +[.contract-item] +[[GovernorComponent-hash_proposal]] +==== `[.contract-item-name]#++hash_proposal++#++(calls: Span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Hashing function used to (re)build the proposal id from the proposal details. + +[.contract-item] +[[GovernorComponent-state]] +==== `[.contract-item-name]#++state++#++(proposal_id: felt252) → ProposalState++` [.item-kind]#external# + +Returns the state of a proposal, given its id. + +[.contract-item] +[[GovernorComponent-proposal_threshold]] +==== `[.contract-item-name]#++proposal_threshold++#++() → u256++` [.item-kind]#external# + +The number of votes required in order for a voter to become a proposer. + +[.contract-item] +[[GovernorComponent-proposal_snapshot]] +==== `[.contract-item-name]#++proposal_snapshot++#++(proposal_id: felt252) → u64++` [.item-kind]#external# + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the +end of this block. Hence, voting for this proposal starts at the beginning of the following block. + +[.contract-item] +[[GovernorComponent-proposal_deadline]] +==== `[.contract-item-name]#++proposal_deadline++#++(proposal_id: felt252) → u64++` [.item-kind]#external# + +Timepoint at which votes close. If using block number, votes close at the end of this block, so +it is possible to cast a vote during this block. + +[.contract-item] +[[GovernorComponent-proposal_proposer]] +==== `[.contract-item-name]#++proposal_proposer++#++(proposal_id: felt252) → ContractAddress++` [.item-kind]#external# + +The account that created a proposal. + +[.contract-item] +[[GovernorComponent-proposal_eta]] +==== `[.contract-item-name]#++proposal_eta++#++(proposal_id: felt252) → u64++` [.item-kind]#external# + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and +`proposal_deadline`, this doesn't use the governor clock, and instead relies on the +executor's clock which may be different. In most cases this will be a timestamp. + +[.contract-item] +[[GovernorComponent-proposal_needs_queuing]] +==== `[.contract-item-name]#++proposal_needs_queuing++#++(proposal_id: felt252) → bool++` [.item-kind]#external# + +Whether a proposal needs to be queued before execution. This indicates if the proposal needs to go through a timelock. + +[.contract-item] +[[GovernorComponent-voting_delay]] +==== `[.contract-item-name]#++voting_delay++#++() → u64++` [.item-kind]#external# + +Delay between when a proposal is created and when the vote starts. The unit this duration is expressed in +depends on the clock (see {ERC-6372}) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + +[.contract-item] +[[GovernorComponent-voting_period]] +==== `[.contract-item-name]#++voting_period++#++() → u64++` [.item-kind]#external# + +Delay between the vote start and vote end. The unit this duration is expressed in depends on +the clock (see {ERC-6372}) this contract uses. + +NOTE: The `voting_delay` can delay the start of the vote. This must be considered when +setting the voting duration compared to the voting delay. + +NOTE: This value is stored when the proposal is submitted so that possible changes to the +value do not affect proposals that have already been submitted. + +[.contract-item] +[[GovernorComponent-quorum]] +==== `[.contract-item-name]#++quorum++#++(timepoint: u64) → u256++` [.item-kind]#external# + +Minimum number of votes required for a proposal to be successful. + +NOTE: The `timepoint` parameter corresponds to the snapshot used for counting vote. This +allows the quorum to scale depending on values such as the total supply of a token at this +timepoint. + +[.contract-item] +[[GovernorComponent-get_votes]] +==== `[.contract-item-name]#++get_votes++#++(account: ContractAddress, timepoint: u64) → u256++` [.item-kind]#external# + +Returns the voting power of an `account` at a specific `timepoint`. + +NOTE: This can be implemented in a number of ways, for example by reading the delegated +balance from one (or multiple) `ERC20Votes` tokens. + +[.contract-item] +[[GovernorComponent-get_votes_with_params]] +==== `[.contract-item-name]#++get_votes_with_params++#++(account: ContractAddress, timepoint: u64, params: Span) → u256++` [.item-kind]#external# + +Returns the voting power of an account at a specific timepoint, given additional encoded parameters. + +[.contract-item] +[[GovernorComponent-has_voted]] +==== `[.contract-item-name]#++has_voted++#++(proposal_id: felt252, account: ContractAddress) → bool++` [.item-kind]#external# + +Returns whether an account has cast a vote on a proposal. + +[.contract-item] +[[GovernorComponent-propose]] +==== `[.contract-item-name]#++propose++#++(calls: Span, description: ByteArray) → felt252++` [.item-kind]#external# + +Creates a new proposal. Voting starts after the delay specified by `voting_delay` and +lasts for a duration specified by `voting_period`. Returns the id of the proposal. + +This function has opt-in frontrunning protection, described in +`is_valid_description_for_proposer`. + +NOTE: The state of the Governor and targets may change between the proposal creation +and its execution. This may be the result of third party actions on the targeted +contracts, or other governor proposals. For example, the balance of this contract could +be updated or its access control permissions may be modified, possibly compromising the +proposal's ability to execute successfully (e.g. the governor doesn't have enough value +to cover a proposal with multiple transfers). + +Requirements: + +- The proposer must be authorized to submit the proposal. +- The proposer must have enough votes to submit the proposal if `proposal_threshold` is +greater than zero. +- The proposal must not already exist. + +Emits a {ProposalCreated} event. + +[.contract-item] +[[GovernorComponent-queue]] +==== `[.contract-item-name]#++queue++#++(calls: Span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Queues a proposal. Some governors require this step to be performed before execution can +happen. If queuing is not necessary, this function may revert. +Queuing a proposal requires the quorum to be reached, the vote to be successful, and the +deadline to be reached. + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Succeeded` state. +- The queue operation must return a non-zero ETA. + +Emits a {ProposalQueued} event. + +[.contract-item] +[[GovernorComponent-execute]] +==== `[.contract-item-name]#++execute++#++(calls: span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Executes a successful proposal. This requires the quorum to be reached, the vote to be +successful, and the deadline to be reached. Depending on the governor it might also be +required that the proposal was queued and that some delay passed. + +NOTE: Some modules can modify the requirements for execution, for example by adding an +additional timelock (See `timelock_controller`). + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Succeeded` or `Queued` state. + +Emits a {ProposalExecuted} event. + +[.contract-item] +[[GovernorComponent-cancel]] +==== `[.contract-item-name]#++cancel++#++(calls: Span, description_hash: felt252) → felt252++` [.item-kind]#external# + +Cancels a proposal. A proposal is cancellable by the proposer, but only while it is +Pending state, i.e. before the vote starts. + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Pending` state. +- The caller must be the proposer of the proposal. + +Emits a {ProposalCanceled} event. + +[.contract-item] +[[GovernorComponent-cast_vote]] +==== `[.contract-item-name]#++cast_vote++#++(proposal_id: felt252, support: u8) → u256++` [.item-kind]#external# + +Cast a vote. + +Requirements: + +- The proposal must be active. + +Emits a {VoteCast} event. + +[.contract-item] +[[GovernorComponent-cast_vote_with_reason]] +==== `[.contract-item-name]#++cast_vote_with_reason++#++(proposal_id: felt252, support: u8, reason: ByteArray) → u256++` [.item-kind]#external# + +Cast a vote with a `reason`. + +Requirements: + +- The proposal must be active. + +Emits a {VoteCast} event. + +[.contract-item] +[[GovernorComponent-cast_vote_with_reason_and_params]] +==== `[.contract-item-name]#++cast_vote_with_reason_and_params++#++(proposal_id: felt252, support: u8, reason: ByteArray, params: Span) → u256++` [.item-kind]#external# + +Cast a vote with a `reason` and additional serialized `params`. + +Requirements: + +- The proposal must be active. + +Emits either: + +- {VoteCast} event if no params are provided. +- {VoteCastWithParams} event otherwise. + +[.contract-item] +[[GovernorComponent-cast_vote_by_sig]] +==== `[.contract-item-name]#++cast_vote_by_sig++#++(proposal_id: felt252, support: u8, voter: ContractAddress, signature: Span) → u256++` [.item-kind]#external# + +Cast a vote using the ``voter``'s signature. + +Requirements: + +- The proposal must be active. +- The nonce in the signed message must match the account's current nonce. +- `voter` must implement `SRC6::is_valid_signature`. +- `signature` must be valid for the message hash. + +Emits a {VoteCast} event. + +[.contract-item] +[[GovernorComponent-cast_vote_with_reason_and_params_by_sig]] +==== `[.contract-item-name]#++cast_vote_with_reason_and_params_by_sig++#++(proposal_id: felt252, support: u8, voter: ContractAddress, reason: ByteArray, params: Span, signature: Span) → u256++` [.item-kind]#external# + +Cast a vote with a `reason` and additional serialized `params` using the `voter`'s +signature. + +Requirements: + +- The proposal must be active. +- The nonce in the signed message must match the account's current nonce. +- `voter` must implement `SRC6::is_valid_signature`. +- `signature` must be valid for the message hash. + +Emits either: + +- {VoteCast} event if no params are provided. +- {VoteCastWithParams} event otherwise. + +[.contract-item] +[[GovernorComponent-nonces]] +==== `[.contract-item-name]#++nonces++#++(voter: ContractAddress) → felt252++` [.item-kind]#external# + +Returns the next unused nonce for an address. + +[.contract-item] +[[GovernorComponent-relay]] +==== `[.contract-item-name]#++relay++#++(call: Call)++` [.item-kind]#external# + +Relays a transaction or function call to an arbitrary target. + +In cases where the governance executor is some contract other than the governor itself, like +when using a timelock, this function can be invoked in a governance proposal to recover +tokens that were sent to the governor contract by mistake. + +NOTE: If the executor is simply the governor itself, use of `relay` is redundant. + +[#GovernorComponent-Internal-Functions] +==== Internal functions + +[.contract-item] +[[GovernorComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState)++` [.item-kind]#internal# + +Initializes the contract by registering the supported interface id. + +[.contract-item] +[[GovernorComponent-get_proposal]] +==== `[.contract-item-name]#++get_proposal++#++(self: @ContractState, proposal_id: felt252) → ProposalCore++` [.item-kind]#internal# + +Returns the proposal object given its id. + +[.contract-item] +[[GovernorComponent-is_valid_description_for_proposer]] +==== `[.contract-item-name]#++is_valid_description_for_proposer++#++(self: @ContractState, proposer: ContractAddress, description: ByteArray) → bool++` [.item-kind]#internal# + +Checks if the proposer is authorized to submit a proposal with the given description. + +If the proposal description ends with `#proposer=0x???`, where `0x???` is an address +written as a hex string (case insensitive), then the submission of this proposal will +only be authorized to said address. + +This is used for frontrunning protection. By adding this pattern at the end of their +proposal, one can ensure that no other address can submit the same proposal. An attacker +would have to either remove or change that part, which would result in a different +proposal id. + +NOTE: In Starknet, the Sequencer ensures the order of transactions, but frontrunning +can still be achieved by nodes, and potentially other actors in the future with +sequencer decentralization. + +If the description does not match this pattern, it is unrestricted and anyone can submit +it. This includes: + +- If the `0x???` part is not a valid hex string. +- If the `0x???` part is a valid hex string, but does not contain exactly 64 hex digits. +- If it ends with the expected suffix followed by newlines or other whitespace. +- If it ends with some other similar suffix, e.g. `#other=abc`. +- If it does not end with any such suffix. + +[.contract-item] +[[GovernorComponent-_hash_proposal]] +==== `[.contract-item-name]#++_hash_proposal++#++(self: @ContractState, calls: Span, description_hash: felt252) → felt252++` [.item-kind]#internal# + +Returns the proposal id computed from the given parameters. + +The proposal id is computed as a Pedersen hash of: + +- The array of calls being proposed +- The description hash + +[.contract-item] +[[GovernorComponent-_proposal_snapshot]] +==== `[.contract-item-name]#++_proposal_snapshot++#++(self: @ContractState, proposal_id: felt252) → u64++` [.item-kind]#internal# + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot +is performed at the end of this block. Hence, voting for this proposal starts at the +beginning of the following block. + +[.contract-item] +[[GovernorComponent-_proposal_deadline]] +==== `[.contract-item-name]#++_proposal_deadline++#++(self: @ContractState, proposal_id: felt252) → u64++` [.item-kind]#internal# + +Timepoint at which votes close. If using block number, votes close at the end of this +block, so it is possible to cast a vote during this block. + +[.contract-item] +[[GovernorComponent-_proposal_proposer]] +==== `[.contract-item-name]#++_proposal_proposer++#++(self: @ContractState, proposal_id: felt252) → ContractAddress++` [.item-kind]#internal# + +The account that created a proposal. + +[.contract-item] +[[GovernorComponent-_proposal_eta]] +==== `[.contract-item-name]#++_proposal_eta++#++(self: @ContractState, proposal_id: felt252) → u64++` [.item-kind]#internal# + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` +and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the +executor's clock which may be different. In most cases this will be a timestamp. + +[.contract-item] +[[GovernorComponent-assert_only_governance]] +==== `[.contract-item-name]#++assert_only_governance++#++(self: @ContractState)++` [.item-kind]#internal# + +Asserts that the caller is the governance executor. + +WARNING: When the executor is not the governor itself (i.e. a timelock), it can call +functions that are restricted with this modifier, and also potentially execute +transactions on behalf of the governor. Because of this, this module is designed to work +with the TimelockController as the unique potential external executor. The timelock +MUST have the governor as the only proposer, canceller, and executor. + +[.contract-item] +[[GovernorComponent-validate_state]] +==== `[.contract-item-name]#++validate_state++#++(self: @ContractState, proposal_id: felt252, state: ProposalState)++` [.item-kind]#internal# + +Validates that a proposal is in the expected state. Otherwise it panics. + +[.contract-item] +[[GovernorComponent-use_nonce]] +==== `[.contract-item-name]#++use_nonce++#++(ref self: ContractState) → felt252++` [.item-kind]#internal# + +Consumes a nonce, returns the current value, and increments nonce. + +[.contract-item] +[[GovernorComponent-_get_votes]] +==== `[.contract-item-name]#++_get_votes++#++(self: @ContractState, account: ContractAddress, timepoint: u64, params: Span) → u256++` [.item-kind]#internal# + +Internal wrapper for `GovernorVotesTrait::get_votes`. + +[.contract-item] +[[GovernorComponent-_proposal_threshold]] +==== `[.contract-item-name]#++_proposal_threshold++#++(self: @ContractState) → u256++` [.item-kind]#internal# + +Internal wrapper for `GovernorProposeTrait::proposal_threshold`. + +[.contract-item] +[[GovernorComponent-_state]] +==== `[.contract-item-name]#++_state++#++(self: @ContractState, proposal_id: felt252) → ProposalState++` [.item-kind]#internal# + +Returns the state of a proposal, given its id. + +Requirements: + +- The proposal must exist. + +[.contract-item] +[[GovernorComponent-_propose]] +==== `[.contract-item-name]#++_propose++#++(ref self: ContractState, calls: Span, description_hash: felt252) → felt252++` [.item-kind]#internal# + +Internal propose mechanism. Returns the proposal id. + +Requirements: + +- The proposal must not already exist. + +Emits a {ProposalCreated} event. + +[.contract-item] +[[GovernorComponent-_cancel]] +==== `[.contract-item-name]#++_cancel++#++(ref self: ContractState, proposal_id: felt252)++` [.item-kind]#internal# + +Internal cancel mechanism with minimal restrictions. + +A proposal can be cancelled in any state other than Canceled or Executed. + +NOTE: Once cancelled, a proposal can't be re-submitted. + +[.contract-item] +[[GovernorComponent-_count_vote]] +==== `[.contract-item-name]#++_count_vote++#++(ref self: ContractState, proposal_id: felt252, account: ContractAddress, support: u8, weight: u256, params: Span)++` [.item-kind]#internal# + +Internal wrapper for `GovernorCountingTrait::count_vote`. + +[.contract-item] +[[GovernorComponent-_cast_vote]] +==== `[.contract-item-name]#++_cast_vote++#++(ref self: ContractState, proposal_id: felt252, account: ContractAddress, support: u8, reason: ByteArray, params: Span) → u256++` [.item-kind]#internal# + +Internal vote-casting mechanism. + +Checks that the vote is pending and that it has not been cast yet. +This function retrieves the voting weight using `get_votes` and then calls +the `_count_vote` internal function. + +Emits either: + +- {VoteCast} event if no params are provided. +- {VoteCastWithParams} event otherwise. + +[#GovernorComponent-Events] +==== Events + +[.contract-item] +[[GovernorComponent-ProposalCreated]] +==== `[.contract-item-name]#++ProposalCreated++#++(proposal_id: felt252, proposer: ContractAddress, calls: Span, signatures: Span>, vote_start: u64, vote_end: u64, description: ByteArray)++` [.item-kind]#event# + +Emitted when a proposal is created. + +[.contract-item] +[[GovernorComponent-ProposalQueued]] +==== `[.contract-item-name]#++ProposalQueued++#++(proposal_id: felt252, eta_seconds: u64)++` [.item-kind]#event# + +Emitted when a proposal is queued. + +[.contract-item] +[[GovernorComponent-ProposalExecuted]] +==== `[.contract-item-name]#++ProposalExecuted++#++(proposal_id: felt252)++` [.item-kind]#event# + +Emitted when a proposal is executed. + +[.contract-item] +[[GovernorComponent-ProposalCanceled]] +==== `[.contract-item-name]#++ProposalCanceled++#++(proposal_id: felt252)++` [.item-kind]#event# + +Emitted when a proposal is canceled. + +[.contract-item] +[[GovernorComponent-VoteCast]] +==== `[.contract-item-name]#++VoteCast++#++(voter: ContractAddress, proposal_id: felt252, support: u8, weight: u256, reason: ByteArray)++` [.item-kind]#event# + +Emitted when a vote is cast. + +[.contract-item] +[[GovernorComponent-VoteCastWithParams]] +==== `[.contract-item-name]#++VoteCastWithParams++#++(voter: ContractAddress, proposal_id: felt252, support: u8, weight: u256, reason: ByteArray, params: Span)++` [.item-kind]#event# + +Emitted when a vote is cast with params. + +== Governor extensions + +:extension-traits: xref:#GovernorComponent-Extensions-Traits-Traits[extensions traits] + +The Governor component can (and must) be extended by implementing the {extension-traits} to add the desired functionality. +This can be achieved by directly implementing the traits on your contract, or by using a set of ready-to-use extensions +provided by the library, which are presented below. + +[.contract] +[[GovernorCoreExecutionComponent]] +=== `++GovernorCoreExecutionComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/extensions/governor_core_execution.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::extensions::GovernorCoreExecutionComponent; +``` + +Extension of {GovernorComponent} providing an execution mechanism directly through +the Governor itself. For a timelocked execution mechanism, see +xref:#GovernorTimelockExecutionComponent[GovernorTimelockExecutionComponent]. + +[.contract-index] +.Extension traits implementations +-- +.GovernorExecution + +* xref:#GovernorCoreExecutionComponent-state[`++state(self, proposal_id)++`] +* xref:#GovernorCoreExecutionComponent-executor[`++executor(self)++`] +* xref:#GovernorCoreExecutionComponent-execute_operations[`++execute_operations(self, proposal_id, calls, description_hash)++`] +* xref:#GovernorCoreExecutionComponent-queue_operations[`++queue_operations(self, proposal_id, calls, description_hash)++`] +* xref:#GovernorCoreExecutionComponent-proposal_needs_queuing[`++proposal_needs_queuing(self, proposal_id)++`] +* xref:#GovernorCoreExecutionComponent-cancel_operations[`++cancel_operations(self, proposal_id, description_hash)++`] +-- + +[#GovernorCoreExecutionComponent-Extension-Traits-Functions] +==== Extension traits functions + +[.contract-item] +[[GovernorCoreExecutionComponent-state]] +==== `[.contract-item-name]#++state++#++(self: @ContractState, proposal_id: felt252) → ProposalState++` [.item-kind]#internal# + +Returns the state of a proposal given its id. + +Requirements: + +- The proposal must exist. + +[.contract-item] +[[GovernorCoreExecutionComponent-executor]] +==== `[.contract-item-name]#++executor++#++(self: @ContractState) → ContractAddress++` [.item-kind]#internal# + +Returns the executor address. + +In this case, it returns the governor contract address since execution is performed directly through it. + +[.contract-item] +[[GovernorCoreExecutionComponent-execute_operations]] +==== `[.contract-item-name]#++execute_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span, description_hash: felt252)++` [.item-kind]#internal# + +Executes the proposal's operations directly through the governor contract. + +[.contract-item] +[[GovernorCoreExecutionComponent-queue_operations]] +==== `[.contract-item-name]#++queue_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span, description_hash: felt252) → u64++` [.item-kind]#internal# + +In this implementation, queuing is not required so it returns 0. + +[.contract-item] +[[GovernorCoreExecutionComponent-proposal_needs_queuing]] +==== `[.contract-item-name]#++proposal_needs_queuing++#++(self: @ContractState, proposal_id: felt252) → bool++` [.item-kind]#internal# + +In this implementation, it always returns false. + +[.contract-item] +[[GovernorCoreExecutionComponent-cancel_operations]] +==== `[.contract-item-name]#++cancel_operations++#++(ref self: ContractState, proposal_id: felt252, description_hash: felt252)++` [.item-kind]#internal# + +Cancels a proposal's operations. + +[.contract] +[[GovernorCountingSimpleComponent]] +=== `++GovernorCountingSimpleComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/extensions/governor_counting_simple.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::extensions::GovernorCountingSimpleComponent; +``` + +Extension of {GovernorComponent} for simple vote counting with three options. + +[.contract-index] +.Extension traits implementations +-- +.GovernorCounting + +* xref:#GovernorCountingSimpleComponent-counting_mode[`counting_mode(self)`] +* xref:#GovernorCountingSimpleComponent-count_vote[`count_vote(self, proposal_id, account, support, total_weight, params)`] +* xref:#GovernorCountingSimpleComponent-has_voted[`has_voted(self, proposal_id, account)`] +* xref:#GovernorCountingSimpleComponent-quorum_reached[`quorum_reached(self, proposal_id)`] +* xref:#GovernorCountingSimpleComponent-vote_succeeded[`vote_succeeded(self, proposal_id)`] +-- + +[#GovernorCountingSimpleComponent-Extension-Traits-Functions] +==== Extension traits functions + +[.contract-item] +[[GovernorCountingSimpleComponent-counting_mode]] +==== `[.contract-item-name]#++counting_mode++#++(self: @ContractState) → ByteArray++` [.item-kind]#internal# + +Returns `"support=bravo&quorum=for,abstain"`. + +- `support=bravo` indicates that the support follows the Governor Bravo format where voters can vote For, Against, or Abstain +- `quorum=for,abstain` indicates that both For and Abstain votes count toward quorum + +[.contract-item] +[[GovernorCountingSimpleComponent-count_vote]] +==== `[.contract-item-name]#++count_vote++#++(ref self: ContractState, proposal_id: felt252, account: ContractAddress, support: u8, total_weight: u256, params: Span) → u256++` [.item-kind]#internal# + +Records a vote for a proposal. + +The support value follows the `VoteType` enum (0=Against, 1=For, 2=Abstain). + +Returns the weight that was counted. + +[.contract-item] +[[GovernorCountingSimpleComponent-has_voted]] +==== `[.contract-item-name]#++has_voted++#++(self: @ContractState, proposal_id: felt252, account: ContractAddress) → bool++` [.item-kind]#internal# + +Returns whether an account has cast a vote on a proposal. + +[.contract-item] +[[GovernorCountingSimpleComponent-quorum_reached]] +==== `[.contract-item-name]#++quorum_reached++#++(self: @ContractState, proposal_id: felt252) → bool++` [.item-kind]#internal# + +Returns whether a proposal has reached quorum. + +In this implementation, both For and Abstain votes count toward quorum. + +[.contract-item] +[[GovernorCountingSimpleComponent-vote_succeeded]] +==== `[.contract-item-name]#++vote_succeeded++#++(self: @ContractState, proposal_id: felt252) → bool++` [.item-kind]#internal# + +Returns whether a proposal has succeeded. + +In this implementation, the For votes must be strictly greater than Against votes. + +[.contract] +[[GovernorSettingsComponent]] +=== `++GovernorSettingsComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/extensions/governor_settings.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::extensions::GovernorSettingsComponent; +``` + +Extension of {GovernorComponent} for settings that are updatable through governance. + +[.contract-index] +.Extension traits implementations +-- +.GovernorSettings + +* xref:#GovernorSettingsComponent-voting_delay[`++voting_delay(self)++`] +* xref:#GovernorSettingsComponent-voting_period[`++voting_period(self)++`] +* xref:#GovernorSettingsComponent-proposal_threshold[`++proposal_threshold(self)++`] +-- + +[.contract-index] +.Embeddable implementations +-- +.GovernorSettingsAdminImpl + +* xref:#GovernorSettingsComponent-set_voting_delay[`++set_voting_delay(self, new_voting_delay)++`] +* xref:#GovernorSettingsComponent-set_voting_period[`++set_voting_period(self, new_voting_period)++`] +* xref:#GovernorSettingsComponent-set_proposal_threshold[`++set_proposal_threshold(self, new_proposal_threshold)++`] +-- + +[.contract-index] +.Internal implementations +-- +.InternalImpl + +* xref:#GovernorSettingsComponent-initializer[`++initializer(self, new_voting_delay, new_voting_period, new_proposal_threshold)++`] +* xref:#GovernorSettingsComponent-assert_only_governance[`++assert_only_governance(self)++`] +* xref:#GovernorSettingsComponent-_set_voting_delay[`++_set_voting_delay(self, new_voting_delay)++`] +* xref:#GovernorSettingsComponent-_set_voting_period[`++_set_voting_period(self, new_voting_period)++`] +* xref:#GovernorSettingsComponent-_set_proposal_threshold[`++_set_proposal_threshold(self, new_proposal_threshold)++`] +-- + +[.contract-index] +.Events +-- +* xref:#GovernorSettingsComponent-VotingDelayUpdated[`++VotingDelayUpdated(old_voting_delay, new_voting_delay)++`] +* xref:#GovernorSettingsComponent-VotingPeriodUpdated[`++VotingPeriodUpdated(old_voting_period, new_voting_period)++`] +* xref:#GovernorSettingsComponent-ProposalThresholdUpdated[`++ProposalThresholdUpdated(old_proposal_threshold, new_proposal_threshold)++`] +-- + +[#GovernorSettings-Extension-Traits-Functions] +==== Extension traits functions + +[.contract-item] +[[GovernorSettingsComponent-voting_delay]] +==== `[.contract-item-name]#++voting_delay++#++(self: @ContractState) → u64++` [.item-kind]#internal# + +Returns the delay, between when a proposal is created and when voting starts. + +[.contract-item] +[[GovernorSettingsComponent-voting_period]] +==== `[.contract-item-name]#++voting_period++#++(self: @ContractState) → u64++` [.item-kind]#internal# + +Returns the time period, during which votes can be cast. + +[.contract-item] +[[GovernorSettingsComponent-proposal_threshold]] +==== `[.contract-item-name]#++proposal_threshold++#++(self: @ContractState) → u256++` [.item-kind]#internal# + +Returns the minimum number of votes required for an account to create a proposal. + +[#GovernorSettings-Embeddable-Functions] +==== Embeddable functions + +:VotingDelayUpdated: xref:#GovernorSettingsComponent-VotingDelayUpdated[VotingDelayUpdated] +:VotingPeriodUpdated: xref:#GovernorSettingsComponent-VotingPeriodUpdated[VotingPeriodUpdated] +:ProposalThresholdUpdated: xref:#GovernorSettingsComponent-ProposalThresholdUpdated[ProposalThresholdUpdated] + +[.contract-item] +[[GovernorSettingsComponent-set_voting_delay]] +==== `[.contract-item-name]#++set_voting_delay++#++(ref self: ContractState, new_voting_delay: u64)++` [.item-kind]#external# + +Sets the voting delay. + +Requirements: + +- Caller must be the governance executor. + +NOTE: This function does not emit an event if the new voting delay is the same as the old one. + +May emit a {VotingDelayUpdated} event. + +[.contract-item] +[[GovernorSettingsComponent-set_voting_period]] +==== `[.contract-item-name]#++set_voting_period++#++(ref self: ContractState, new_voting_period: u64)++` [.item-kind]#external# + +Sets the voting period. + +NOTE: This function does not emit an event if the new voting period is the same as the +old one. + +Requirements: + +- Caller must be the governance executor. +- `new_voting_period` must be greater than 0. + +May emit a {VotingPeriodUpdated} event. + +[.contract-item] +[[GovernorSettingsComponent-set_proposal_threshold]] +==== `[.contract-item-name]#++set_proposal_threshold++#++(ref self: ContractState, new_proposal_threshold: u256)++` [.item-kind]#external# + +Sets the proposal threshold. + +NOTE: This function does not emit an event if the new proposal threshold is the same as +the old one. + +Requirements: + +- Caller must be the governance executor. + +May emit a {ProposalThresholdUpdated} event. + +[#GovernorSettingsComponent-Internal-Functions] +==== Internal functions + +[.contract-item] +[[GovernorSettingsComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, initial_voting_delay: u64, initial_voting_period: u64, initial_proposal_threshold: u256)++` [.item-kind]#internal# + +Initializes the component by setting the default values. + +Requirements: + +- `new_voting_period` must be greater than 0. + +Emits a {VotingDelayUpdated}, {VotingPeriodUpdated}, and {ProposalThresholdUpdated} event. + +[.contract-item] +[[GovernorSettingsComponent-assert_only_governance]] +==== `[.contract-item-name]#++assert_only_governance++#++(ref self: ContractState)++` [.item-kind]#internal# + +Asserts that the caller is the governance executor. + +[.contract-item] +[[GovernorSettingsComponent-_set_voting_delay]] +==== `[.contract-item-name]#++_set_voting_delay++#++(ref self: ContractState, new_voting_delay: u64)++` [.item-kind]#internal# + +Internal function to update the voting delay. + +NOTE: This function does not emit an event if the new voting delay is the same as the +old one. + +May emit a {VotingDelayUpdated} event. + +[.contract-item] +[[GovernorSettingsComponent-_set_voting_period]] +==== `[.contract-item-name]#++_set_voting_period++#++(ref self: ContractState, new_voting_period: u64)++` [.item-kind]#internal# + +Internal function to update the voting period. + +Requirements: + +- `new_voting_period` must be greater than 0. + +NOTE: This function does not emit an event if the new voting period is the same as the old one. + +May emit a {VotingPeriodUpdated} event. + +[.contract-item] +[[GovernorSettingsComponent-_set_proposal_threshold]] +==== `[.contract-item-name]#++_set_proposal_threshold++#++(ref self: ContractState, new_proposal_threshold: u256)++` [.item-kind]#internal# + +Internal function to update the proposal threshold. + +NOTE: This function does not emit an event if the new proposal threshold is the same as the old one. + +May emit a {ProposalThresholdUpdated} event. + +[#GovernorSettings-Events] +==== Events + +[.contract-item] +[[GovernorSettingsComponent-VotingDelayUpdated]] +==== `[.contract-item-name]#++VotingDelayUpdated++#++(old_voting_delay: u64, new_voting_delay: u64)++` [.item-kind]#event# + +Emitted when the voting delay is updated. + +[.contract-item] +[[GovernorSettingsComponent-VotingPeriodUpdated]] +==== `[.contract-item-name]#++VotingPeriodUpdated++#++(old_voting_period: u64, new_voting_period: u64)++` [.item-kind]#event# + +Emitted when the voting period is updated. + +[.contract-item] +[[GovernorSettingsComponent-ProposalThresholdUpdated]] +==== `[.contract-item-name]#++ProposalThresholdUpdated++#++(old_proposal_threshold: u256, new_proposal_threshold: u256)++` [.item-kind]#event# + +Emitted when the proposal threshold is updated. + +[.contract] +[[GovernorVotesComponent]] +=== `++GovernorVotesComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/extensions/governor_votes.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::extensions::GovernorVotesComponent; +``` + +Extension of {GovernorComponent} for voting weight extraction from a token with the {IVotes} +extension. + +[.contract-index] +.Extension traits implementations +-- +.GovernorVotes + +* xref:#GovernorVotesComponent-clock[`++clock(self)++`] +* xref:#GovernorVotesComponent-clock_mode[`++clock_mode(self)++`] +* xref:#GovernorVotesComponent-get_votes[`++get_votes(self, account, timepoint, params)++`] +-- + +[.contract-index] +.Embeddable implementations +-- +.VotesTokenImpl + +* xref:#GovernorVotesComponent-token[`++token(self)++`] +-- + +[.contract-index] +.Internal implementations +-- +.InternalImpl + +* xref:#GovernorVotesComponent-initializer[`++initializer(self, votes_token)++`] +-- + +[#GovernorVotes-Extension-Traits-Functions] +==== Extension traits functions + +[.contract-item] +[[GovernorVotesComponent-clock]] +==== `[.contract-item-name]#++clock++#++(self: @ContractState) → u64++` [.item-kind]#internal# + +Returns the current timepoint according to the time mode the contract is operating in. + +In this implementation, returns the current block timestamp. + +NOTE: {VotesComponent} always uses the block timestamp for tracking checkpoints. +This must be updated in order to allow for more flexible clock modes. + +[.contract-item] +[[GovernorVotesComponent-clock_mode]] +==== `[.contract-item-name]#++clock_mode++#++(self: @ContractState) → ByteArray++` [.item-kind]#internal# + +Returns `"mode=timestamp&from=starknet::SN_MAIN"`. + +See https://eips.ethereum.org/EIPS/eip-6372#clock_mode + +[.contract-item] +[[GovernorVotesComponent-get_votes]] +==== `[.contract-item-name]#++get_votes++#++(self: @ContractState, account: ContractAddress, timepoint: u64, params: Span) → u256++` [.item-kind]#internal# + +Returns the voting power of `account` at a specific `timepoint` using the votes token. + +[[GovernorVotesComponent-Embeddable-Functions]] +==== Embeddable functions + +[.contract-item] +[[GovernorVotesComponent-token]] +==== `[.contract-item-name]#++token++#++(self: @ContractState) → ContractAddress++` [.item-kind]#external# + +Returns the votes token that voting power is sourced from. + +[#GovernorVotesComponent-Internal-Functions] +==== Internal functions + +[.contract-item] +[[GovernorVotesComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, votes_token: ContractAddress)++` [.item-kind]#internal# + +Initializes the component by setting the votes token. + +Requirements: + +- `votes_token` must not be zero. + +:QuorumNumeratorUpdated: xref:#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated[QuorumNumeratorUpdated] + +[.contract] +[[GovernorVotesQuorumFractionComponent]] +=== `++GovernorVotesQuorumFractionComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent; +``` + +Extension of {GovernorComponent} for voting weight extraction from a token with the +{IVotes} extension and a quorum expressed as a fraction of the total supply. + +[.contract-index] +.Extension traits implementations +-- +.GovernorQuorum + +* xref:#GovernorVotesQuorumFractionComponent-quorum[`++quorum(self, timepoint)++`] + +.GovernorVotes + +* xref:#GovernorVotesQuorumFractionComponent-clock[`++clock(self)++`] +* xref:#GovernorVotesQuorumFractionComponent-clock_mode[`++clock_mode(self)++`] +* xref:#GovernorVotesQuorumFractionComponent-get_votes[`++get_votes(self, account, timepoint, params)++`] +-- + +[.contract-index] +.Embeddable implementations +-- +.QuorumFractionImpl + +* xref:#GovernorVotesQuorumFractionComponent-token[`++token(self)++`] +* xref:#GovernorVotesQuorumFractionComponent-current_quorum_numerator[`++current_quorum_numerator(self)++`] +* xref:#GovernorVotesQuorumFractionComponent-quorum_numerator[`++quorum_numerator(self, timepoint)++`] +* xref:#GovernorVotesQuorumFractionComponent-quorum_denominator[`++quorum_denominator(self)++`] +-- + +[.contract-index] +.Internal implementations +-- +.InternalImpl + +* xref:#GovernorVotesQuorumFractionComponent-initializer[`++initializer(self, votes_token, quorum_numerator)++`] +* xref:#GovernorVotesQuorumFractionComponent-update_quorum_numerator[`++update_quorum_numerator(self, new_quorum_numerator)++`] +-- + +[.contract-index] +.Events +-- +* xref:#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated[`++QuorumNumeratorUpdated(old_quorum_numerator, new_quorum_numerator)++`] +-- + +[#GovernorVotesQuorumFractionComponent-Extension-Traits-Functions] +==== Extension traits functions + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-quorum]] +==== `[.contract-item-name]#++quorum++#++(self: @ContractState, timepoint: u64) → u256++` [.item-kind]#internal# + +Returns a percentage of the votes token total supply at a given `timepoint` in the past. + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-clock]] +==== `[.contract-item-name]#++clock++#++(self: @ContractState) → u64++` [.item-kind]#internal# + +Returns the current timepoint according to the time mode the contract is operating in. + +In this implementation, returns the current block timestamp. + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-clock_mode]] +==== `[.contract-item-name]#++clock_mode++#++(self: @ContractState) → ByteArray++` [.item-kind]#internal# + +Returns `"mode=timestamp&from=starknet::SN_MAIN"`. + +See https://eips.ethereum.org/EIPS/eip-6372#clock_mode + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-get_votes]] +==== `[.contract-item-name]#++get_votes++#++(self: @ContractState, account: ContractAddress, timepoint: u64, params: Span) → u256++` [.item-kind]#internal# + +Returns the voting power of `account` at a specific `timepoint` using the votes token. + +[#GovernorVotesQuorumFractionComponent-Embeddable-Functions] +==== Embeddable functions + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-token]] +==== `[.contract-item-name]#++token++#++(self: @ContractState) → ContractAddress++` [.item-kind]#external# + +Returns the address of the votes token used for voting power extraction. + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-current_quorum_numerator]] +==== `[.contract-item-name]#++current_quorum_numerator++#++(self: @ContractState) → u256++` [.item-kind]#external# + +Returns the current quorum numerator value. + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-quorum_numerator]] +==== `[.contract-item-name]#++quorum_numerator++#++(self: @ContractState, timepoint: u64) → u256++` [.item-kind]#external# + +Returns the quorum numerator value at a specific `timepoint` in the past. + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-quorum_denominator]] +==== `[.contract-item-name]#++quorum_denominator++#++(self: @ContractState) → u256++` [.item-kind]#external# + +Returns the quorum denominator value. + +[#GovernorVotesQuorumFractionComponent-Internal-Functions] +==== Internal functions + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(self: @ComponentState, votes_token: ContractAddress, quorum_numerator: u256)++` [.item-kind]#internal# + +Initializes the component by setting the votes token and the initial quorum numerator value. + +Requirements: + +* `votes_token` must not be zero. +* `quorum_numerator` must be less than `quorum_denominator`. + +Emits a {QuorumNumeratorUpdated} event. + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-update_quorum_numerator]] +==== `[.contract-item-name]#++update_quorum_numerator++#++(self: @ComponentState, new_quorum_numerator: u256)++` [.item-kind]#internal# + +Updates the quorum numerator. + +NOTE: This function does not emit an event if the new quorum numerator is the same as the old one. + +Requirements: + +* `new_quorum_numerator` must be less than `quorum_denominator`. + +May emit a {QuorumNumeratorUpdated} event. + +[#GovernorVotesQuorumFractionComponent-Events] +==== Events + +[.contract-item] +[[GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated]] +==== `[.contract-item-name]#++QuorumNumeratorUpdated++#++(old_quorum_numerator: u256, new_quorum_numerator: u256)++` [.item-kind]#event# + +Emitted when the quorum numerator is updated. + +:roles: xref:governance/timelock.adoc#roles + +[.contract] +[[GovernorTimelockExecutionComponent]] +=== `++GovernorTimelockExecutionComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/governance/src/governor/extensions/governor_timelock_execution.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent; +``` + +Extension of {GovernorComponent} that binds the execution process to an instance of a contract +implementing {TimelockControllerComponent}. This adds a delay, enforced by the timelock +to all successful proposals (in addition to the voting duration). + +NOTE: The Governor needs the {roles}[PROPOSER, EXECUTOR, and CANCELLER roles] to work properly. + +Using this model means the proposal will be operated by the timelock and not by the +governor. Thus, the assets and permissions must be attached to the timelock. Any asset +sent to the governor will be inaccessible from a proposal, unless executed via +`Governor::relay`. + +WARNING: Setting up the timelock to have additional proposers or cancellers besides +the governor is very risky, as it grants them the ability to: 1) execute operations as the +timelock, and thus possibly performing operations or accessing funds that are expected to only +be accessible through a vote, and 2) block governance proposals that have been approved by the +voters, effectively executing a Denial of Service attack. + +[.contract-index] +.Extension traits implementations +-- +.GovernorExecution + +* xref:#GovernorTimelockExecutionComponent-state[`++state(self, proposal_id)++`] +* xref:#GovernorTimelockExecutionComponent-executor[`++executor(self)++`] +* xref:#GovernorTimelockExecutionComponent-execute_operations[`++execute_operations(self, proposal_id, calls, description_hash)++`] +* xref:#GovernorTimelockExecutionComponent-queue_operations[`++queue_operations(self, proposal_id, calls, description_hash)++`] +* xref:#GovernorTimelockExecutionComponent-proposal_needs_queuing[`++proposal_needs_queuing(self, proposal_id)++`] +* xref:#GovernorTimelockExecutionComponent-cancel_operations[`++cancel_operations(self, proposal_id, description_hash)++`] +-- + +[.contract-index] +.Embeddable implementations +-- +.TimelockedImpl + +* xref:#GovernorTimelockExecutionComponent-timelock[`++timelock(self)++`] +* xref:#GovernorTimelockExecutionComponent-get_timelock_id[`++get_timelock_id(self, proposal_id)++`] +* xref:#GovernorTimelockExecutionComponent-update_timelock[`++update_timelock(self, new_timelock)++`] +-- + +[.contract-index] +.Internal implementations +-- +.InternalImpl + +* xref:#GovernorTimelockExecutionComponent-initializer[`++initializer(self, timelock_controller)++`] +* xref:#GovernorTimelockExecutionComponent-assert_only_governance[`++assert_only_governance(self)++`] +* xref:#GovernorTimelockExecutionComponent-timelock_salt[`++timelock_salt(self, description_hash)++`] +* xref:#GovernorTimelockExecutionComponent-get_timelock_dispatcher[`++get_timelock_dispatcher(self)++`] +* xref:#GovernorTimelockExecutionComponent-_update_timelock[`++_update_timelock(self, new_timelock)++`] + +-- + +[.contract-index] +.Events +-- +* xref:#GovernorTimelockExecutionComponent-TimelockUpdated[`++TimelockUpdated(old_timelock, new_timelock)++`] +-- + +[#GovernorTimelockExecutionComponent-Extension-Traits-Functions] +==== Extension traits functions + +[.contract-item] +[[GovernorTimelockExecutionComponent-state]] +==== `[.contract-item-name]#++state++#++(self: @ContractState, proposal_id: felt252) → ProposalState++` [.item-kind]#internal# + +Returns the state of a proposal given its id. + +Requirements: + +- The proposal must exist. + +[.contract-item] +[[GovernorTimelockExecutionComponent-executor]] +==== `[.contract-item-name]#++executor++#++(self: @ContractState) → ContractAddress++` [.item-kind]#internal# + +Returns the executor address. + +In this module, the executor is the timelock controller. + +[.contract-item] +[[GovernorTimelockExecutionComponent-execute_operations]] +==== `[.contract-item-name]#++execute_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span, description_hash: felt252)++` [.item-kind]#internal# + +Runs the already queued proposal through the timelock. + +[.contract-item] +[[GovernorTimelockExecutionComponent-queue_operations]] +==== `[.contract-item-name]#++queue_operations++#++(ref self: ContractState, proposal_id: felt252, calls: Span, description_hash: felt252) → u64++` [.item-kind]#internal# + +Queue a proposal to the timelock. + +Returns the eta for the execution of the queued proposal. + +[.contract-item] +[[GovernorTimelockExecutionComponent-proposal_needs_queuing]] +==== `[.contract-item-name]#++proposal_needs_queuing++#++(self: @ContractState, proposal_id: felt252) → bool++` [.item-kind]#internal# + +In this implementation, it always returns true. + +[.contract-item] +[[GovernorTimelockExecutionComponent-cancel_operations]] +==== `[.contract-item-name]#++cancel_operations++#++(ref self: ContractState, proposal_id: felt252, description_hash: felt252)++` [.item-kind]#internal# + +Cancels the timelocked proposal if it has already been queued. + +[#GovernorTimelockExecutionComponent-Embeddable-Functions] +==== Embeddable functions + +:TimelockUpdated: xref:#GovernorTimelockExecutionComponent-TimelockUpdated[TimelockUpdated] + +[.contract-item] +[[GovernorTimelockExecutionComponent-timelock]] +==== `[.contract-item-name]#++timelock++#++(self: @ContractState) → ContractAddress++` [.item-kind]#external# + +Returns the timelock controller address. + +[.contract-item] +[[GovernorTimelockExecutionComponent-get_timelock_id]] +==== `[.contract-item-name]#++get_timelock_id++#++(self: @ContractState) → felt252++` [.item-kind]#external# + +Returns the timelock proposal id for a given proposal id. + +[.contract-item] +[[GovernorTimelockExecutionComponent-update_timelock]] +==== `[.contract-item-name]#++update_timelock++#++(ref self: ContractState, new_timelock: ContractAddress)++` [.item-kind]#external# + +Updates the associated timelock. + +Requirements: + +- The caller must be the governance. + +Emits a {TimelockUpdated} event. + +[#GovernorTimelockExecutionComponent-Internal-Functions] +==== Internal functions + +[.contract-item] +[[GovernorTimelockExecutionComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, timelock: ContractAddress)++` [.item-kind]#internal# + +Initializes the timelock controller. + +Requirements: + +- The timelock must not be the zero address. + +[.contract-item] +[[GovernorTimelockExecutionComponent-assert_only_governance]] +==== `[.contract-item-name]#++assert_only_governance++#++(self: @ContractState)++` [.item-kind]#internal# + +Ensures the caller is the executor (the timelock controller in this case). + +[.contract-item] +[[GovernorTimelockExecutionComponent-timelock_salt]] +==== `[.contract-item-name]#++timelock_salt++#++(self: @ContractState, description_hash: felt252) → felt252++` [.item-kind]#internal# + +Computes the `TimelockController` operation salt. + +It is computed with the governor address itself to avoid collisions across +governor instances using the same timelock. + +[.contract-item] +[[GovernorTimelockExecutionComponent-get_timelock_dispatcher]] +==== `[.contract-item-name]#++get_timelock_dispatcher++#++(self: @ContractState) → ITimelockDispatcher++` [.item-kind]#internal# + +Returns a dispatcher for interacting with the timelock controller. + +[.contract-item] +[[GovernorTimelockExecutionComponent-_update_timelock]] +==== `[.contract-item-name]#++_update_timelock++#++(ref self: ContractState, new_timelock: ContractAddress)++` [.item-kind]#internal# + +Internal function to update the timelock controller address. + +Emits a {TimelockUpdated} event. + +[#GovernorTimelockExecutionComponent-Events] +==== Events + +[.contract-item] +[[GovernorTimelockExecutionComponent-TimelockUpdated]] +==== `[.contract-item-name]#++TimelockUpdated++#++(old_timelock: ContractAddress, new_timelock: ContractAddress)++` [.item-kind]#event# + +Emitted when the timelock controller is updated. + == Timelock In a governance system, `TimelockControllerComponent` is in charge of introducing a delay between a proposal and its execution. @@ -276,21 +2242,21 @@ Component that implements <> and enables the implementing [.sub-index#TimelockControllerComponent-Embeddable-Impls-TimelockImpl] .TimelockImpl -* xref:#TimelockControllerComponent-is_operation[`++is_operation(id)++`] -* xref:#TimelockControllerComponent-is_operation_pending[`++is_operation_pending(id)++`] -* xref:#TimelockControllerComponent-is_operation_ready[`++is_operation_ready(id)++`] -* xref:#TimelockControllerComponent-is_operation_done[`++is_operation_done(id)++`] -* xref:#TimelockControllerComponent-get_timestamp[`++get_timestamp(id)++`] -* xref:#TimelockControllerComponent-get_operation_state[`++get_operation_state(id)++`] -* xref:#TimelockControllerComponent-get_min_delay[`++get_min_delay()++`] -* xref:#TimelockControllerComponent-hash_operation[`++hash_operation(call, predecessor, salt)++`] -* xref:#TimelockControllerComponent-hash_operation_batch[`++hash_operation_batch(calls, predecessor, salt)++`] -* xref:#TimelockControllerComponent-schedule[`++schedule(call, predecessor, salt, delay)++`] -* xref:#TimelockControllerComponent-schedule_batch[`++schedule_batch(calls, predecessor, salt, delay)++`] -* xref:#TimelockControllerComponent-cancel[`++cancel(id)++`] -* xref:#TimelockControllerComponent-execute[`++execute(call, predecessor, salt)++`] -* xref:#TimelockControllerComponent-execute_batch[`++execute_batch(calls, predecessor, salt)++`] -* xref:#TimelockControllerComponent-update_delay[`++update_delay(new_delay)++`] +* xref:#TimelockControllerComponent-is_operation[`++is_operation(self, id)++`] +* xref:#TimelockControllerComponent-is_operation_pending[`++is_operation_pending(self, id)++`] +* xref:#TimelockControllerComponent-is_operation_ready[`++is_operation_ready(self, id)++`] +* xref:#TimelockControllerComponent-is_operation_done[`++is_operation_done(self, id)++`] +* xref:#TimelockControllerComponent-get_timestamp[`++get_timestamp(self, id)++`] +* xref:#TimelockControllerComponent-get_operation_state[`++get_operation_state(self, id)++`] +* xref:#TimelockControllerComponent-get_min_delay[`++get_min_delay(self)++`] +* xref:#TimelockControllerComponent-hash_operation[`++hash_operation(self, call, predecessor, salt)++`] +* xref:#TimelockControllerComponent-hash_operation_batch[`++hash_operation_batch(self, calls, predecessor, salt)++`] +* xref:#TimelockControllerComponent-schedule[`++schedule(self, call, predecessor, salt, delay)++`] +* xref:#TimelockControllerComponent-schedule_batch[`++schedule_batch(self, calls, predecessor, salt, delay)++`] +* xref:#TimelockControllerComponent-cancel[`++cancel(self, id)++`] +* xref:#TimelockControllerComponent-execute[`++execute(self, call, predecessor, salt)++`] +* xref:#TimelockControllerComponent-execute_batch[`++execute_batch(self, calls, predecessor, salt)++`] +* xref:#TimelockControllerComponent-update_delay[`++update_delay(self, new_delay)++`] .SRC5Impl * xref:api/introspection.adoc#ISRC5-supports_interface[`supports_interface(self, interface_id: felt252)`] @@ -336,7 +2302,7 @@ Component that implements <> and enables the implementing -- [#TimelockControllerComponent-Functions] -==== Functions +==== Embeddable functions [.contract-item] [[TimelockControllerComponent-is_operation]] @@ -676,7 +2642,7 @@ Delegates votes from the sender to `delegatee`. [[IVotes-delegate_by_sig]] ==== `[.contract-item-name]#++delegate_by_sig++#++(delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span)++` [.item-kind]#external# -Delegates votes from `delegator` to `delegatee` through a SNIP12 message signature validation. +Delegates votes from `delegator` to `delegatee` through a {SNIP-12} message signature validation. [.contract] [[VotesComponent]] @@ -739,7 +2705,7 @@ NOTE: When using this module, your contract must implement the {VotingUnitsTrait [.contract-item] [[VotesComponent-ERC20VotesImpl-get_voting_units]] -==== `[.contract-item-name]#++get_voting_units++#++(self: @ComponentState, account: ContractAddress) → u256++` [.item-kind]#internal# +==== `[.contract-item-name]#++get_voting_units++#++(self: @ContractState, account: ContractAddress) → u256++` [.item-kind]#internal# Returns the number of voting units for a given account. @@ -758,7 +2724,7 @@ may compromise the internal vote accounting. [.contract-item] [[VotesComponent-ERC721VotesImpl-get_voting_units]] -==== `[.contract-item-name]#++get_voting_units++#++(self: @ComponentState, account: ContractAddress) → u256++` [.item-kind]#internal# +==== `[.contract-item-name]#++get_voting_units++#++(self: @ContractState, account: ContractAddress) → u256++` [.item-kind]#internal# Returns the number of voting units for a given account. @@ -774,17 +2740,17 @@ Any deviation from this formula when transferring voting units (e.g. by using ho may compromise the internal vote accounting. [#VotesComponent-Functions] -==== Functions +==== Embeddable functions [.contract-item] [[VotesComponent-get_votes]] -==== `[.contract-item-name]#++get_votes++#++(self: @ComponentState, account: ContractAddress) → u256++` [.item-kind]#external# +==== `[.contract-item-name]#++get_votes++#++(self: @ContractState, account: ContractAddress) → u256++` [.item-kind]#external# Returns the current amount of votes that `account` has. [.contract-item] [[VotesComponent-get_past_votes]] -==== `[.contract-item-name]#++get_past_votes++#++(self: @ComponentState, account: ContractAddress, timepoint: u64) → u256++` [.item-kind]#external# +==== `[.contract-item-name]#++get_past_votes++#++(self: @ContractState, account: ContractAddress, timepoint: u64) → u256++` [.item-kind]#external# Returns the amount of votes that `account` had at a specific moment in the past. @@ -794,7 +2760,7 @@ Requirements: [.contract-item] [[VotesComponent-get_past_total_supply]] -==== `[.contract-item-name]#++get_past_total_supply++#++(self: @ComponentState, timepoint: u64) → u256++` [.item-kind]#external# +==== `[.contract-item-name]#++get_past_total_supply++#++(self: @ContractState, timepoint: u64) → u256++` [.item-kind]#external# Returns the total supply of votes available at a specific moment in the past. @@ -808,13 +2774,13 @@ Requirements: [.contract-item] [[VotesComponent-delegates]] -==== `[.contract-item-name]#++delegates++#++(self: @ComponentState, account: ContractAddress) → ContractAddress++` [.item-kind]#external# +==== `[.contract-item-name]#++delegates++#++(self: @ContractState, account: ContractAddress) → ContractAddress++` [.item-kind]#external# Returns the delegate that `account` has chosen. [.contract-item] [[VotesComponent-delegate]] -==== `[.contract-item-name]#++delegate++#++(ref self: ComponentState, delegatee: ContractAddress)++` [.item-kind]#external# +==== `[.contract-item-name]#++delegate++#++(ref self: ContractState, delegatee: ContractAddress)++` [.item-kind]#external# Delegates votes from the sender to `delegatee`. @@ -824,9 +2790,9 @@ May emit one or two {DelegateVotesChanged} events. [.contract-item] [[VotesComponent-delegate_by_sig]] -==== `[.contract-item-name]#++delegate_by_sig++#++(ref self: ComponentState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span)++` [.item-kind]#external# +==== `[.contract-item-name]#++delegate_by_sig++#++(ref self: ContractState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span)++` [.item-kind]#external# -Delegates votes from `delegator` to `delegatee` through a SNIP12 message signature validation. +Delegates votes from `delegator` to `delegatee` through a {SNIP-12} message signature validation. Requirements: @@ -844,13 +2810,13 @@ May emit one or two {DelegateVotesChanged} events. [.contract-item] [[VotesComponent-get_total_supply]] -==== `[.contract-item-name]#++get_total_supply++#++(self: @ComponentState) → u256++` [.item-kind]#internal# +==== `[.contract-item-name]#++get_total_supply++#++(self: @ContractState) → u256++` [.item-kind]#internal# Returns the current total supply of votes. [.contract-item] [[VotesComponent-move_delegate_votes]] -==== `[.contract-item-name]#++move_delegate_votes++#++(ref self: ComponentState, from: ContractAddress, to: ContractAddress, amount: u256)++` [.item-kind]#internal# +==== `[.contract-item-name]#++move_delegate_votes++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, amount: u256)++` [.item-kind]#internal# Moves delegated votes from one delegate to another. @@ -858,7 +2824,7 @@ May emit one or two {DelegateVotesChanged} events. [.contract-item] [[VotesComponent-transfer_voting_units]] -==== `[.contract-item-name]#++transfer_voting_units++#++(ref self: ComponentState, from: ContractAddress, to: ContractAddress, amount: u256)++` [.item-kind]#internal# +==== `[.contract-item-name]#++transfer_voting_units++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, amount: u256)++` [.item-kind]#internal# Transfers, mints, or burns voting units. @@ -871,19 +2837,19 @@ May emit one or two {DelegateVotesChanged} events. [.contract-item] [[VotesComponent-num_checkpoints]] -==== `[.contract-item-name]#++num_checkpoints++#++(self: @ComponentState, account: ContractAddress) → u64++` [.item-kind]#internal# +==== `[.contract-item-name]#++num_checkpoints++#++(self: @ContractState, account: ContractAddress) → u64++` [.item-kind]#internal# Returns the number of checkpoints for `account`. [.contract-item] [[VotesComponent-checkpoints]] -==== `[.contract-item-name]#++checkpoints++#++(self: @ComponentState, account: ContractAddress, pos: u64) → Checkpoint++` [.item-kind]#internal# +==== `[.contract-item-name]#++checkpoints++#++(self: @ContractState, account: ContractAddress, pos: u64) → Checkpoint++` [.item-kind]#internal# Returns the `pos`-th checkpoint for `account`. [.contract-item] [[VotesComponent-_delegate]] -==== `[.contract-item-name]#++_delegate++#++(ref self: ComponentState, account: ContractAddress, delegatee: ContractAddress)++` [.item-kind]#internal# +==== `[.contract-item-name]#++_delegate++#++(ref self: ContractState, account: ContractAddress, delegatee: ContractAddress)++` [.item-kind]#internal# Delegates all of ``account``'s voting units to `delegatee`. diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc deleted file mode 100644 index 83f2280fc..000000000 --- a/docs/modules/ROOT/pages/governance.adoc +++ /dev/null @@ -1,427 +0,0 @@ -= Governance - -:timelock-component: xref:api/governance.adoc#TimelockControllerComponent[TimelockControllerComponent] -:votes-component: xref:api/governance.adoc#VotesComponent[VotesComponent] -:accesscontrol-component: xref:api/access.adoc#AccessControlComponent[AccessControlComponent] -:src5-component: xref:api/introspection.adoc#SRC5Component[SRC5Component] -:delegate: xref:api/governance.adoc#VotesComponent-delegate[delegate] -:delegate_by_sig: xref:api/governance.adoc#VotesComponent-delegate_by_sig[delegate_by_sig] -:voting_units_trait: xref:api/governance.adoc#VotingUnitsTrait[VotingUnitsTrait] -:votes-usage: xref:../governance.adoc#usage_2[usage] -:nonces-component: xref:api/utilities.adoc#NoncesComponent[NoncesComponent] -:snip12-metadata: xref:api/utilities.adoc#snip12[SNIP12Metadata] - -Decentralized protocols are in constant evolution from the moment they are publicly released. -Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. -The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc. - -The Contracts for Cairo library aims to build a modular system of governance components for users to easily integrate and customize in their contracts. - -== Timelock Controller - -The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. - -NOTE: The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. - -=== Operation lifecycle - -Timelocked operations are identified by a unique id (their hash) and follow a specific `OperationState` lifecycle: - -`Unset` → `Waiting` → `Ready` → `Done` - -=== Timelock flow - -==== Schedule - -:schedule: xref:api/governance.adoc#ITimelock-schedule[schedule] -:get_timestamp: xref:api/governance.adoc#ITimelock-get_timestamp[get_timestamp] - -When a proposer calls {schedule}, the `OperationState` moves from `Unset` to `Waiting`. -This starts a timer that must be greater than or equal to the minimum delay. -The timer expires at a timestamp accessible through {get_timestamp}. -Once the timer expires, the `OperationState` automatically moves to the `Ready` state. -At this point, it can be executed. - -==== Execute - -:execute: xref:api/governance.adoc#ITimelock-execute[execute] - -By calling {execute}, an executor triggers the operation's underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor's operation must be in the `Done` state for this transaction to succeed. - -==== Cancel - -:cancel: xref:api/governance.adoc#ITimelock-cancel[cancel] - -The {cancel} function allows cancellers to cancel any pending operations. -This resets the operation to the `Unset` state. -It is therefore possible for a proposer to re-schedule an operation that has been cancelled. -In this case, the timer restarts when the operation is re-scheduled. - -=== Roles - -{timelock-component} leverages an {accesscontrol-component} setup that we need to understand in order to set up roles. - -- `PROPOSER_ROLE` - in charge of queueing operations. - -- `CANCELLER_ROLE` - may cancel scheduled operations. -During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. -Therefore, the initial proposers may also cancel operations after they are scheduled. - -- `EXECUTOR_ROLE` - in charge of executing already available operations. - -- `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. - -CAUTION: The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. -The latter case may be required to ease a contract's initial configuration; however, this role should promptly be renounced. - -Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. -This allows anyone to execute an operation once it's in the `Ready` OperationState. -To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. - -CAUTION: Be very careful with enabling open roles as _anyone_ can call the function. - -=== Minimum delay - -:get_min_delay: xref:api/governance.adoc#ITimelock-get_min_delay[get_min_delay] - -The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. -The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. - -After initialization, the only way to change the timelock's minimum delay is to schedule it and execute it with the same flow as any other operation. - -The minimum delay of a contract is accessible through {get_min_delay}. - -=== Usage - -Integrating the timelock into a contract requires integrating {timelock-component} as well as {src5-component} and {accesscontrol-component} as dependencies. -The contract's constructor should initialize the timelock which consists of setting the: - -- Proposers and executors. -- Minimum delay between scheduling and executing an operation. -- Optional admin if additional configuration is required. - -NOTE: The optional admin should renounce their role once configuration is complete. - -Here's an example of a simple timelock contract: - -[,cairo] ----- -#[starknet::contract] -mod TimelockControllerContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_governance::timelock::TimelockControllerComponent; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ContractAddress; - - component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); - component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Timelock Mixin - #[abi(embed_v0)] - impl TimelockMixinImpl = - TimelockControllerComponent::TimelockMixinImpl; - impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - access_control: AccessControlComponent::Storage, - #[substorage(v0)] - timelock: TimelockControllerComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - TimelockEvent: TimelockControllerComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - min_delay: u64, - proposers: Span, - executors: Span, - admin: ContractAddress - ) { - self.timelock.initializer(min_delay, proposers, executors, admin); - } -} ----- - -=== Interface - -This is the full interface of the TimelockMixinImpl implementation: - -[,cairo] ----- -#[starknet::interface] -pub trait TimelockABI { - // ITimelock - fn is_operation(self: @TState, id: felt252) -> bool; - fn is_operation_pending(self: @TState, id: felt252) -> bool; - fn is_operation_ready(self: @TState, id: felt252) -> bool; - fn is_operation_done(self: @TState, id: felt252) -> bool; - fn get_timestamp(self: @TState, id: felt252) -> u64; - fn get_operation_state(self: @TState, id: felt252) -> OperationState; - fn get_min_delay(self: @TState) -> u64; - fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; - fn hash_operation_batch( - self: @TState, calls: Span, predecessor: felt252, salt: felt252 - ) -> felt252; - fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); - fn schedule_batch( - ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 - ); - fn cancel(ref self: TState, id: felt252); - fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); - fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); - fn update_delay(ref self: TState, new_delay: u64); - - // ISRC5 - fn supports_interface(self: @TState, interface_id: felt252) -> bool; - - // IAccessControl - fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(self: @TState, role: felt252) -> felt252; - fn grant_role(ref self: TState, role: felt252, account: ContractAddress); - fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); - fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(self: @TState, role: felt252) -> felt252; - fn grantRole(ref self: TState, role: felt252, account: ContractAddress); - fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); - fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); -} ----- - -== Votes - -The {votes-component} provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. - -NOTE: By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. - -IMPORTANT: The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the {votes-usage} section for examples of how to implement this. - -=== Key Features - -1. *Delegation*: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the {delegate} function directly, or by providing a signature to be used with {delegate_by_sig}. -2. *Historical lookups*: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp. + -This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. - -=== Usage - -When integrating the `VotesComponent`, the {voting_units_trait} must be implemented to get the voting units for a given account as a function of the implementing contract. + -For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated. + -Additionally, you must implement the {nonces-component} and the {snip12-metadata} trait to enable delegation by signatures. - -Here's an example of how to structure a simple ERC20Votes contract: - - -[source,cairo] ----- -#[starknet::contract] -mod ERC20VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_token::erc20::ERC20Component; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc20_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20VotesEvent: VotesComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - // Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. - impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { - fn after_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - let mut contract_state = self.get_contract_mut(); - contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc20.initializer("MyToken", "MTK"); - } -} ----- - -And here's an example of how to structure a simple ERC721Votes contract: - - -[source,cairo] ----- -#[starknet::contract] -pub mod ERC721VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721Component; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC721 - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc721_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc721: ERC721Component::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721VotesEvent: VotesComponent::Event, - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - /// Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `before_update` hook of the - //`ERC721Component::ERC721HooksTrait`. - // This hook is called before the transfer is executed. - // This gives us access to the previous owner. - impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { - fn before_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) { - let mut contract_state = self.get_contract_mut(); - - // We use the internal function here since it does not check if the token - // id exists which is necessary for mints - let previous_owner = self._owner_of(token_id); - contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721.initializer("MyToken", "MTK", ""); - } -} ----- - -=== Interface - -This is the full interface of the `VotesImpl` implementation: -[source,cairo] ----- -#[starknet::interface] -pub trait VotesABI { - // IVotes - fn get_votes(self: @TState, account: ContractAddress) -> u256; - fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; - fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; - fn delegate(ref self: TState, delegatee: ContractAddress); - fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); - - // INonces - fn nonces(self: @TState, owner: ContractAddress) -> felt252; -} ----- diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc new file mode 100644 index 000000000..3a84a5c98 --- /dev/null +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -0,0 +1,388 @@ += Governor +:votes-component: xref:api/governance.adoc#VotesComponent[VotesComponent] +:governor-component: xref:api/governance.adoc#GovernorComponent[GovernorComponent] +:access-control: xref:access.adoc#role_based_accesscontrol[AccessControl] +:timelock-controller: xref:governance/timelock.adoc[TimelockController] +:ivotes: xref:api/governance.adoc#IVotes[IVotes] + +include::../utils/_common.adoc[] + +Decentralized protocols are in constant evolution from the moment they are publicly released. Often, +the initial team retains control of this evolution in the first stages, but eventually delegates it +to a community of stakeholders. The process by which this community makes decisions is called +on-chain governance, and it has become a central component of decentralized protocols, fueling +varied decisions such as parameter tweaking, smart contract upgrades, integrations with other +protocols, treasury management, grants, etc. + +This governance protocol is generally implemented in a special-purpose contract called “Governor”. In +OpenZeppelin Contracts for Cairo, we set out to build a modular system of Governor components where different +requirements can be accommodated by implementing specific traits. You will find the most common requirements out of the box, +but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. + +== Usage and setup + +=== Token + +The voting power of each account in our governance setup will be determined by an ERC20 or an ERC721 token. The token has +to implement the {votes-component} extension. This extension will keep track of historical balances so that voting power +is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting. + +If your project already has a live token that does not include Votes and is not upgradeable, you can wrap it in a +governance token by using a wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. + +NOTE: The library currently does not include a wrapper for tokens, but it will be added in a future release. + +NOTE: Currently, the clock mode is fixed to block timestamps, since the Votes component uses the block timestamp to track +checkpoints. We plan to add support for more flexible clock modes in Votes in a future release, allowing to use, for example, +block numbers instead. + +=== Governor + +We will initially build a Governor without a timelock. The core logic is given by the {governor-component}, but we +still need to choose: + +1) how voting power is determined, + +2) how many votes are needed for quorum, + +3) what options people have when casting a vote and how those votes are counted, and + +4) the execution mechanism that should be used. + +Each of these aspects is customizable by writing your own extensions, +or more easily choosing one from the library. + +:GovernorVotes: xref:api/governance.adoc#GovernorVotesComponent[GovernorVotes] +:GovernorVotesQuorumFraction: xref:api/governance.adoc#GovernorVotesQuorumFractionComponent[GovernorVotesQuorumFraction] +:GovernorCountingSimple: xref:api/governance.adoc#GovernorCountingSimpleComponent[GovernorCountingSimple] +:GovernorCoreExecution: xref:api/governance.adoc#GovernorCoreExecutionComponent[GovernorCoreExecution] +:GovernorSettings: xref:api/governance.adoc#GovernorSettingsComponent[GovernorSettings] +:GovernorTimelockExecution: xref:api/governance.adoc#GovernorTimelockExecutionComponent[GovernorTimelockExecution] +:GovernorSettingsTrait: xref:api/governance.adoc#GovernorComponent[GovernorSettingsTrait] + +**For 1)** we will use the {GovernorVotes} extension, which hooks to an {ivotes} instance to determine the voting power +of an account based on the token balance they hold when a proposal becomes active. +This module requires the address of the token to be passed as an argument to the initializer. + +**For 2)** we will use {GovernorVotesQuorumFraction}. This works together with the {ivotes} instance to define the quorum as a +percentage of the total supply at the block when a proposal’s voting power is retrieved. This requires an initializer +parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%, so we will initialize +the module with parameter 40 since the quorum denominator is 1000 for precision (this indicates the percentage, +resulting in 4%). + +**For 3)** we will use {GovernorCountingSimple}, an extension that offers 3 options to voters: For, Against, and Abstain, +and where only For and Abstain votes are counted towards quorum. + +**For 4)** we will use {GovernorCoreExecution}, an extension that allows proposal execution directly through the governor. + +NOTE: Another option is {GovernorTimelockExecution}. An example can be found in the next section. + +Besides these, we also need an implementation for the {GovernorSettingsTrait} defining the voting delay, voting period, +and proposal threshold. While we can use the {GovernorSettings} extension which allows to set these parameters by the +governor itself, we will implement the trait locally in the contract and set the voting delay, voting period, +and proposal threshold as constant values. + +__voting_delay__: How long after a proposal is created should voting power be fixed. A large voting delay gives +users time to unstake tokens if necessary. + +__voting_period__: How long does a proposal remain open to votes. + +NOTE: These parameters are specified in the unit defined in the token’s clock, which is for now always timestamps. + +__proposal_threshold__: This restricts proposal creation to accounts who have enough voting power. + +An implementation of `GovernorComponent::ImmutableConfig` is also required. For the example below, we have used +the `DefaultConfig`. Check the {immutable-config} guide for more details. + +The last missing step is to add an `SNIP12Metadata` implementation used to retrieve the name and version of the governor. + +[, cairo] +---- +#[starknet::contract] +mod MyGovernor { + use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent::InternalTrait; + use openzeppelin_governance::governor::extensions::{ + GovernorVotesQuorumFractionComponent, GovernorCountingSimpleComponent, + GovernorCoreExecutionComponent, + }; + use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + pub const VOTING_DELAY: u64 = 86400; // 1 day + pub const VOTING_PERIOD: u64 = 432_000; // 1 week + pub const PROPOSAL_THRESHOLD: u256 = 10; + pub const QUORUM_NUMERATOR: u256 = 40; // 4% + + component!(path: GovernorComponent, storage: governor, event: GovernorEvent); + component!( + path: GovernorVotesQuorumFractionComponent, + storage: governor_votes, + event: GovernorVotesEvent + ); + component!( + path: GovernorCountingSimpleComponent, + storage: governor_counting_simple, + event: GovernorCountingSimpleEvent + ); + component!( + path: GovernorCoreExecutionComponent, + storage: governor_core_execution, + event: GovernorCoreExecutionEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Governor + #[abi(embed_v0)] + impl GovernorImpl = GovernorComponent::GovernorImpl; + + // Extensions external + #[abi(embed_v0)] + impl QuorumFractionImpl = + GovernorVotesQuorumFractionComponent::QuorumFractionImpl; + + // Extensions internal + impl GovernorQuorumImpl = GovernorVotesQuorumFractionComponent::GovernorQuorum; + impl GovernorVotesImpl = GovernorVotesQuorumFractionComponent::GovernorVotes; + impl GovernorCountingSimpleImpl = + GovernorCountingSimpleComponent::GovernorCounting; + impl GovernorCoreExecutionImpl = + GovernorCoreExecutionComponent::GovernorExecution; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub governor: GovernorComponent::Storage, + #[substorage(v0)] + pub governor_votes: GovernorVotesQuorumFractionComponent::Storage, + #[substorage(v0)] + pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, + #[substorage(v0)] + pub governor_core_execution: GovernorCoreExecutionComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + GovernorEvent: GovernorComponent::Event, + #[flat] + GovernorVotesEvent: GovernorVotesQuorumFractionComponent::Event, + #[flat] + GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, + #[flat] + GovernorCoreExecutionEvent: GovernorCoreExecutionComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, votes_token: ContractAddress) { + self.governor.initializer(); + self.governor_votes.initializer(votes_token, QUORUM_NUMERATOR); + } + + // + // SNIP12 Metadata + // + + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // + // Locally implemented extensions + // + + pub impl GovernorSettings of GovernorComponent::GovernorSettingsTrait { + /// See `GovernorComponent::GovernorSettingsTrait::voting_delay`. + fn voting_delay(self: @GovernorComponent::ComponentState) -> u64 { + VOTING_DELAY + } + + /// See `GovernorComponent::GovernorSettingsTrait::voting_period`. + fn voting_period(self: @GovernorComponent::ComponentState) -> u64 { + VOTING_PERIOD + } + + /// See `GovernorComponent::GovernorSettingsTrait::proposal_threshold`. + fn proposal_threshold(self: @GovernorComponent::ComponentState) -> u256 { + PROPOSAL_THRESHOLD + } + } +} +---- + +=== Timelock + +It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree +with a decision before it is executed. We will use OpenZeppelin’s {timelock-controller} in combination with the +GovernorTimelockExecution extension. + +IMPORTANT: When using a timelock, it is the timelock that will execute proposals and thus the timelock that should +hold any funds, ownership, and access control roles. + +TimelockController uses an {access-control} setup that we need to understand in order to set up roles. + +The Proposer role is in charge of queueing operations: this is the role the Governor instance must be granted, +and it MUST be the only proposer (and canceller) in the system. + +The Executor role is in charge of executing already available operations: we can assign this role to the special +zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). + +The Canceller role is in charge of canceling operations: the Governor instance must be granted this role, +and it MUST be the only canceller in the system. + +Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role. + +The following example uses the GovernorTimelockExecution extension, together with GovernorSettings, and uses a +fixed quorum value instead of a percentage: + +[, cairo] +---- +#[starknet::contract] +pub mod MyTimelockedGovernor { + use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorSettingsComponent::InternalTrait as GovernorSettingsInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent::InternalTrait as GovernorTimelockExecutionInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorVotesComponent::InternalTrait as GovernorVotesInternalTrait; + use openzeppelin_governance::governor::extensions::{ + GovernorVotesComponent, GovernorSettingsComponent, GovernorCountingSimpleComponent, + GovernorTimelockExecutionComponent + }; + use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + pub const VOTING_DELAY: u64 = 86400; // 1 day + pub const VOTING_PERIOD: u64 = 432_000; // 1 week + pub const PROPOSAL_THRESHOLD: u256 = 10; + pub const QUORUM: u256 = 100_000_000; + + component!(path: GovernorComponent, storage: governor, event: GovernorEvent); + component!(path: GovernorVotesComponent, storage: governor_votes, event: GovernorVotesEvent); + component!( + path: GovernorSettingsComponent, storage: governor_settings, event: GovernorSettingsEvent + ); + component!( + path: GovernorCountingSimpleComponent, + storage: governor_counting_simple, + event: GovernorCountingSimpleEvent + ); + component!( + path: GovernorTimelockExecutionComponent, + storage: governor_timelock_execution, + event: GovernorTimelockExecutionEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Governor + #[abi(embed_v0)] + impl GovernorImpl = GovernorComponent::GovernorImpl; + + // Extensions external + #[abi(embed_v0)] + impl VotesTokenImpl = GovernorVotesComponent::VotesTokenImpl; + #[abi(embed_v0)] + impl GovernorSettingsAdminImpl = + GovernorSettingsComponent::GovernorSettingsAdminImpl; + #[abi(embed_v0)] + impl TimelockedImpl = + GovernorTimelockExecutionComponent::TimelockedImpl; + + // Extensions internal + impl GovernorVotesImpl = GovernorVotesComponent::GovernorVotes; + impl GovernorSettingsImpl = GovernorSettingsComponent::GovernorSettings; + impl GovernorCountingSimpleImpl = + GovernorCountingSimpleComponent::GovernorCounting; + impl GovernorTimelockExecutionImpl = + GovernorTimelockExecutionComponent::GovernorExecution; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub governor: GovernorComponent::Storage, + #[substorage(v0)] + pub governor_votes: GovernorVotesComponent::Storage, + #[substorage(v0)] + pub governor_settings: GovernorSettingsComponent::Storage, + #[substorage(v0)] + pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, + #[substorage(v0)] + pub governor_timelock_execution: GovernorTimelockExecutionComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + GovernorEvent: GovernorComponent::Event, + #[flat] + GovernorVotesEvent: GovernorVotesComponent::Event, + #[flat] + GovernorSettingsEvent: GovernorSettingsComponent::Event, + #[flat] + GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, + #[flat] + GovernorTimelockExecutionEvent: GovernorTimelockExecutionComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, votes_token: ContractAddress, timelock_controller: ContractAddress + ) { + self.governor.initializer(); + self.governor_votes.initializer(votes_token); + self.governor_settings.initializer(VOTING_DELAY, VOTING_PERIOD, PROPOSAL_THRESHOLD); + self.governor_timelock_execution.initializer(timelock_controller); + } + + // + // SNIP12 Metadata + // + + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // + // Locally implemented extensions + // + + impl GovernorQuorum of GovernorComponent::GovernorQuorumTrait { + /// See `GovernorComponent::GovernorQuorumTrait::quorum`. + fn quorum(self: @GovernorComponent::ComponentState, timepoint: u64) -> u256 { + QUORUM + } + } +} +---- diff --git a/docs/modules/ROOT/pages/governance/timelock.adoc b/docs/modules/ROOT/pages/governance/timelock.adoc new file mode 100644 index 000000000..7fb13fef6 --- /dev/null +++ b/docs/modules/ROOT/pages/governance/timelock.adoc @@ -0,0 +1,192 @@ += Timelock Controller + +:timelock-component: xref:api/governance.adoc#TimelockControllerComponent[TimelockControllerComponent] +:accesscontrol-component: xref:api/access.adoc#AccessControlComponent[AccessControlComponent] +:src5-component: xref:api/introspection.adoc#SRC5Component[SRC5Component] + + +The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. + +NOTE: The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. + +== Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific `OperationState` lifecycle: + +`Unset` → `Waiting` → `Ready` → `Done` + +== Timelock flow + +=== Schedule + +:schedule: xref:api/governance.adoc#ITimelock-schedule[schedule] +:get_timestamp: xref:api/governance.adoc#ITimelock-get_timestamp[get_timestamp] + +When a proposer calls {schedule}, the `OperationState` moves from `Unset` to `Waiting`. +This starts a timer that must be greater than or equal to the minimum delay. +The timer expires at a timestamp accessible through {get_timestamp}. +Once the timer expires, the `OperationState` automatically moves to the `Ready` state. +At this point, it can be executed. + +=== Execute + +:execute: xref:api/governance.adoc#ITimelock-execute[execute] + +By calling {execute}, an executor triggers the operation's underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor's operation must be in the `Done` state for this transaction to succeed. + +=== Cancel + +:cancel: xref:api/governance.adoc#ITimelock-cancel[cancel] + +The {cancel} function allows cancellers to cancel any pending operations. +This resets the operation to the `Unset` state. +It is therefore possible for a proposer to re-schedule an operation that has been cancelled. +In this case, the timer restarts when the operation is re-scheduled. + +=== Roles + +{timelock-component} leverages an {accesscontrol-component} setup that we need to understand in order to set up roles. + +- `PROPOSER_ROLE` - in charge of queueing operations. + +- `CANCELLER_ROLE` - may cancel scheduled operations. +During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. +Therefore, the initial proposers may also cancel operations after they are scheduled. + +- `EXECUTOR_ROLE` - in charge of executing already available operations. + +- `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. + +CAUTION: The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. +The latter case may be required to ease a contract's initial configuration; however, this role should promptly be renounced. + +Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. +This allows anyone to execute an operation once it's in the `Ready` OperationState. +To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. + +CAUTION: Be very careful with enabling open roles as _anyone_ can call the function. + +=== Minimum delay + +:get_min_delay: xref:api/governance.adoc#ITimelock-get_min_delay[get_min_delay] + +The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. +The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. + +After initialization, the only way to change the timelock's minimum delay is to schedule it and execute it with the same flow as any other operation. + +The minimum delay of a contract is accessible through {get_min_delay}. + +=== Usage + +Integrating the timelock into a contract requires integrating {timelock-component} as well as {src5-component} and {accesscontrol-component} as dependencies. +The contract's constructor should initialize the timelock which consists of setting the: + +- Proposers and executors. +- Minimum delay between scheduling and executing an operation. +- Optional admin if additional configuration is required. + +NOTE: The optional admin should renounce their role once configuration is complete. + +Here's an example of a simple timelock contract: + +[,cairo] +---- +#[starknet::contract] +mod TimelockControllerContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_governance::timelock::TimelockControllerComponent; + use openzeppelin_introspection::src5::SRC5Component; + use starknet::ContractAddress; + + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Timelock Mixin + #[abi(embed_v0)] + impl TimelockMixinImpl = + TimelockControllerComponent::TimelockMixinImpl; + impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + access_control: AccessControlComponent::Storage, + #[substorage(v0)] + timelock: TimelockControllerComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + TimelockEvent: TimelockControllerComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + min_delay: u64, + proposers: Span, + executors: Span, + admin: ContractAddress + ) { + self.timelock.initializer(min_delay, proposers, executors, admin); + } +} +---- + +=== Interface + +This is the full interface of the TimelockMixinImpl implementation: + +[,cairo] +---- +#[starknet::interface] +pub trait TimelockABI { + // ITimelock + fn is_operation(self: @TState, id: felt252) -> bool; + fn is_operation_pending(self: @TState, id: felt252) -> bool; + fn is_operation_ready(self: @TState, id: felt252) -> bool; + fn is_operation_done(self: @TState, id: felt252) -> bool; + fn get_timestamp(self: @TState, id: felt252) -> u64; + fn get_operation_state(self: @TState, id: felt252) -> OperationState; + fn get_min_delay(self: @TState) -> u64; + fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; + fn hash_operation_batch( + self: @TState, calls: Span, predecessor: felt252, salt: felt252 + ) -> felt252; + fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); + fn schedule_batch( + ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 + ); + fn cancel(ref self: TState, id: felt252); + fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); + fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); + fn update_delay(ref self: TState, new_delay: u64); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IAccessControl + fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(self: @TState, role: felt252) -> felt252; + fn grant_role(ref self: TState, role: felt252, account: ContractAddress); + fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); + fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(self: @TState, role: felt252) -> felt252; + fn grantRole(ref self: TState, role: felt252, account: ContractAddress); + fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); + fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); +} +---- diff --git a/docs/modules/ROOT/pages/governance/votes.adoc b/docs/modules/ROOT/pages/governance/votes.adoc new file mode 100644 index 000000000..d66ea7041 --- /dev/null +++ b/docs/modules/ROOT/pages/governance/votes.adoc @@ -0,0 +1,229 @@ += Votes + +:votes-component: xref:api/governance.adoc#VotesComponent[VotesComponent] +:delegate: xref:api/governance.adoc#VotesComponent-delegate[delegate] +:delegate_by_sig: xref:api/governance.adoc#VotesComponent-delegate_by_sig[delegate_by_sig] +:voting_units_trait: xref:api/governance.adoc#VotingUnitsTrait[VotingUnitsTrait] +:votes-usage: xref:../governance.adoc#usage_2[usage] +:nonces-component: xref:api/utilities.adoc#NoncesComponent[NoncesComponent] +:snip12-metadata: xref:api/utilities.adoc#snip12[SNIP12Metadata] + + +The {votes-component} provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. + +NOTE: By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + +IMPORTANT: The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the {votes-usage} section for examples of how to implement this. + +== Key Features + +1. *Delegation*: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the {delegate} function directly, or by providing a signature to be used with {delegate_by_sig}. +2. *Historical lookups*: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp. + +This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. + +== Usage + +When integrating the `VotesComponent`, the {voting_units_trait} must be implemented to get the voting units for a given account as a function of the implementing contract. + +For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated. + +Additionally, you must implement the {nonces-component} and the {snip12-metadata} trait to enable delegation by signatures. + +Here's an example of how to structure a simple ERC20Votes contract: + + +[source,cairo] +---- +#[starknet::contract] +mod ERC20VotesContract { + use openzeppelin_governance::votes::VotesComponent; + use openzeppelin_token::erc20::ERC20Component; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + // Votes + #[abi(embed_v0)] + impl VotesImpl = VotesComponent::VotesImpl; + impl VotesInternalImpl = VotesComponent::InternalImpl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // Nonces + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc20_votes: VotesComponent::Storage, + #[substorage(v0)] + pub erc20: ERC20Component::Storage, + #[substorage(v0)] + pub nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20VotesEvent: VotesComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + // Required for hash computation. + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // We need to call the `transfer_voting_units` function after + // every mint, burn and transfer. + // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. + impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { + fn after_update( + ref self: ERC20Component::ComponentState, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); + } + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer("MyToken", "MTK"); + } +} +---- + +And here's an example of how to structure a simple ERC721Votes contract: + + +[source,cairo] +---- +#[starknet::contract] +pub mod ERC721VotesContract { + use openzeppelin_governance::votes::VotesComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::ERC721Component; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + // Votes + #[abi(embed_v0)] + impl VotesImpl = VotesComponent::VotesImpl; + impl VotesInternalImpl = VotesComponent::InternalImpl; + + // ERC721 + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // Nonces + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc721_votes: VotesComponent::Storage, + #[substorage(v0)] + pub erc721: ERC721Component::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + #[substorage(v0)] + pub nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721VotesEvent: VotesComponent::Event, + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + /// Required for hash computation. + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // We need to call the `transfer_voting_units` function after + // every mint, burn and transfer. + // For this, we use the `before_update` hook of the + //`ERC721Component::ERC721HooksTrait`. + // This hook is called before the transfer is executed. + // This gives us access to the previous owner. + impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut contract_state = self.get_contract_mut(); + + // We use the internal function here since it does not check if the token + // id exists which is necessary for mints + let previous_owner = self._owner_of(token_id); + contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); + } + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721.initializer("MyToken", "MTK", ""); + } +} +---- + +== Interface + +This is the full interface of the `VotesImpl` implementation: +[source,cairo] +---- +#[starknet::interface] +pub trait VotesABI { + // IVotes + fn get_votes(self: @TState, account: ContractAddress) -> u256; + fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; + fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; + fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; + fn delegate(ref self: TState, delegatee: ContractAddress); + fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); + + // INonces + fn nonces(self: @TState, owner: ContractAddress) -> felt252; +} +---- diff --git a/packages/governance/src/governor/extensions/governor_core_execution.cairo b/packages/governance/src/governor/extensions/governor_core_execution.cairo index 9ccddf98a..87cf2b2cc 100644 --- a/packages/governance/src/governor/extensions/governor_core_execution.cairo +++ b/packages/governance/src/governor/extensions/governor_core_execution.cairo @@ -36,6 +36,10 @@ pub mod GovernorCoreExecutionComponent { +Drop > of GovernorComponent::GovernorExecutionTrait { /// See `GovernorComponent::GovernorExecutionTrait::state`. + /// + /// Requirements: + /// + /// - The proposal must exist. fn state( self: @GovernorComponentState, proposal_id: felt252 ) -> ProposalState { @@ -43,11 +47,15 @@ pub mod GovernorCoreExecutionComponent { } /// See `GovernorComponent::GovernorExecutionTrait::executor`. + /// + /// Returns the governor contract address since execution is performed directly through it. fn executor(self: @GovernorComponentState) -> ContractAddress { starknet::get_contract_address() } /// See `GovernorComponent::GovernorExecutionTrait::execute_operations`. + /// + /// Executes the proposal's operations directly through the governor contract. fn execute_operations( ref self: GovernorComponentState, proposal_id: felt252, @@ -61,6 +69,8 @@ pub mod GovernorCoreExecutionComponent { } /// See `GovernorComponent::GovernorExecutionTrait::queue_operations`. + /// + /// In this implementation, queuing is not required so it returns 0. fn queue_operations( ref self: GovernorComponentState, proposal_id: felt252, @@ -71,6 +81,8 @@ pub mod GovernorCoreExecutionComponent { } /// See `GovernorComponent::GovernorExecutionTrait::proposal_needs_queuing`. + /// + /// In this implementation, it always returns false. fn proposal_needs_queuing( self: @GovernorComponentState, proposal_id: felt252 ) -> bool { diff --git a/packages/governance/src/governor/extensions/governor_counting_simple.cairo b/packages/governance/src/governor/extensions/governor_counting_simple.cairo index 55b4705e9..0125b9213 100644 --- a/packages/governance/src/governor/extensions/governor_counting_simple.cairo +++ b/packages/governance/src/governor/extensions/governor_counting_simple.cairo @@ -133,6 +133,8 @@ pub mod GovernorCountingSimpleComponent { } /// See `GovernorComponent::GovernorCountingTrait::quorum_reached`. + /// + /// In this implementation, both For and Abstain votes count toward quorum. fn quorum_reached( self: @GovernorComponentState, proposal_id: felt252 ) -> bool { diff --git a/packages/governance/src/governor/extensions/governor_settings.cairo b/packages/governance/src/governor/extensions/governor_settings.cairo index 0a1ba55f1..1b8611fa0 100644 --- a/packages/governance/src/governor/extensions/governor_settings.cairo +++ b/packages/governance/src/governor/extensions/governor_settings.cairo @@ -4,7 +4,7 @@ /// # GovernorSettings Component /// -/// Extension of GovernorComponent for settings updatable through governance. +/// Extension of GovernorComponent for settings that are updatable through governance. #[starknet::component] pub mod GovernorSettingsComponent { use crate::governor::GovernorComponent::{ diff --git a/packages/governance/src/governor/extensions/governor_timelock_execution.cairo b/packages/governance/src/governor/extensions/governor_timelock_execution.cairo index 39fd848ad..bb72981a4 100644 --- a/packages/governance/src/governor/extensions/governor_timelock_execution.cairo +++ b/packages/governance/src/governor/extensions/governor_timelock_execution.cairo @@ -6,7 +6,7 @@ /// /// Extension of GovernorComponent that binds the execution process to an instance of a contract /// implementing TimelockControllerComponent. This adds a delay, enforced by the TimelockController -/// to all successful proposal (in addition to the voting duration). +/// to all successful proposals (in addition to the voting duration). /// /// NOTE: The Governor needs the PROPOSER, EXECUTOR, and CANCELLER roles to work properly. /// @@ -80,6 +80,10 @@ pub mod GovernorTimelockExecutionComponent { +Drop > of GovernorComponent::GovernorExecutionTrait { /// See `GovernorComponent::GovernorExecutionTrait::state`. + /// + /// Requirements: + /// + /// - The proposal must exist. fn state( self: @GovernorComponentState, proposal_id: felt252 ) -> ProposalState { @@ -212,7 +216,7 @@ pub mod GovernorTimelockExecutionComponent { +GovernorComponent::HasComponent, +Drop > of ITimelocked> { - /// Returns the token that voting power is sourced from. + /// Returns the timelock controller address. fn timelock(self: @ComponentState) -> ContractAddress { self.Governor_timelock_controller.read() } diff --git a/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo b/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo index 1753cf2e9..3bde2c673 100644 --- a/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo +++ b/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo @@ -54,6 +54,8 @@ pub mod GovernorVotesQuorumFractionComponent { +Drop > of GovernorComponent::GovernorQuorumTrait { /// See `GovernorComponent::GovernorQuorumTrait::quorum`. + /// + /// Returns a percentage of the votes token total supply at a given timepoint in the past. fn quorum(self: @GovernorComponentState, timepoint: u64) -> u256 { let contract = self.get_contract(); let this_component = GovernorVotesQuorumFraction::get_component(contract); diff --git a/packages/governance/src/governor/governor.cairo b/packages/governance/src/governor/governor.cairo index bf1439e5e..77030daf4 100644 --- a/packages/governance/src/governor/governor.cairo +++ b/packages/governance/src/governor/governor.cairo @@ -530,6 +530,7 @@ pub mod GovernorComponent { /// Requirements: /// /// - The proposal must be in the `Pending` state. + /// - The caller must be the proposer of the proposal. /// /// Emits a `ProposalCanceled` event. fn cancel( @@ -877,6 +878,10 @@ pub mod GovernorComponent { } /// Returns the state of a proposal, given its id. + /// + /// Requirements: + /// + /// - The proposal must exist. fn _state(self: @ComponentState, proposal_id: felt252) -> ProposalState { let proposal = self.get_proposal(proposal_id); @@ -959,7 +964,7 @@ pub mod GovernorComponent { /// Internal cancel mechanism with minimal restrictions. /// A proposal can be cancelled in any state other than Canceled or Executed. /// - /// NOTE: Once cancelled a proposal can't be re-submitted. + /// NOTE: Once cancelled, a proposal can't be re-submitted. fn _cancel( ref self: ComponentState, proposal_id: felt252, @@ -991,10 +996,11 @@ pub mod GovernorComponent { self.count_vote(proposal_id, account, support, total_weight, params) } - /// Internal vote casting mechanism. + /// Internal vote-casting mechanism. /// - /// Checks that the vote is pending, that it has not been cast yet, retrieve - /// voting weight using `get_votes` and call the `_count_vote` internal function. + /// Checks that the vote is pending and that it has not been cast yet. + /// This function retrieves the voting weight using `get_votes` and then calls + /// the `_count_vote` internal function. /// /// Emits either: /// - `VoteCast` event if no params are provided. diff --git a/packages/governance/src/governor/interface.cairo b/packages/governance/src/governor/interface.cairo index 6bc7bad1b..a7adf9310 100644 --- a/packages/governance/src/governor/interface.cairo +++ b/packages/governance/src/governor/interface.cairo @@ -4,13 +4,13 @@ use starknet::ContractAddress; use starknet::account::Call; -pub const IGOVERNOR_ID: felt252 = 0x1f; // TODO: Update this value. +pub const IGOVERNOR_ID: felt252 = 0x1100a1f8546595b5bd75a6cd8fcc5b015370655e66f275963321c5cd0357ac9; /// Interface for a contract that implements the ERC-6372 standard. #[starknet::interface] pub trait IERC6372 { /// Clock used for flagging checkpoints. - /// Can be overridden to implement timestamp based checkpoints (and voting). + /// Can be overridden to implement block number based checkpoints (and voting). fn clock(self: @TState) -> u64; /// Description of the clock. @@ -116,7 +116,7 @@ pub trait IGovernor { /// Voting power of an `account` at a specific `timepoint`. /// - /// NOTE: this can be implemented in a number of ways, for example by reading the delegated + /// NOTE: This can be implemented in a number of ways, for example by reading the delegated /// balance from one (or multiple) `ERC20Votes` tokens. fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; @@ -215,3 +215,74 @@ pub trait IGovernor { /// NOTE: If the executor is simply the governor itself, use of `relay` is redundant. fn relay(ref self: TState, call: Call); } + +pub trait IGovernor2 { + fn name() -> felt252; + + fn version() -> felt252; + + fn COUNTING_MODE() -> ByteArray; + + fn hash_proposal(calls: Span, description_hash: felt252) -> felt252; + + // fn state(proposal_id: felt252) -> ProposalState; + + fn proposal_threshold() -> u256; + + fn proposal_snapshot(proposal_id: felt252) -> u64; + + fn proposal_deadline(proposal_id: felt252) -> u64; + + fn proposal_proposer(proposal_id: felt252) -> ContractAddress; + + fn proposal_eta(proposal_id: felt252) -> u64; + + fn proposal_needs_queuing(proposal_id: felt252) -> bool; + + fn voting_delay() -> u64; + + fn voting_period() -> u64; + + fn quorum(timepoint: u64) -> u256; + + fn get_votes(account: ContractAddress, timepoint: u64) -> u256; + + fn get_votes_with_params( + account: ContractAddress, timepoint: u64, params: Span + ) -> u256; + + fn has_voted(proposal_id: felt252, account: ContractAddress) -> bool; + + fn propose(calls: Span, description: ByteArray) -> felt252; + + fn queue(calls: Span, description_hash: felt252) -> felt252; + + fn execute(calls: Span, description_hash: felt252) -> felt252; + + fn cancel(calls: Span, description_hash: felt252) -> felt252; + + fn cast_vote(proposal_id: felt252, support: u8) -> u256; + + fn cast_vote_with_reason(proposal_id: felt252, support: u8, reason: ByteArray) -> u256; + + fn cast_vote_with_reason_and_params( + proposal_id: felt252, support: u8, reason: ByteArray, params: Span + ) -> u256; + + fn cast_vote_by_sig( + proposal_id: felt252, support: u8, voter: ContractAddress, signature: Span + ) -> u256; + + fn cast_vote_with_reason_and_params_by_sig( + proposal_id: felt252, + support: u8, + voter: ContractAddress, + reason: ByteArray, + params: Span, + signature: Span + ) -> u256; + + fn nonces(voter: ContractAddress) -> felt252; + + fn relay(call: Call); +}