From 626b68d23a9469deee869dd2ad5d4789ec9b1175 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 27 Nov 2024 15:37:47 +0100 Subject: [PATCH 1/9] docs: document main component --- docs/modules/ROOT/nav.adoc | 7 +- docs/modules/ROOT/pages/api/erc721.adoc | 1 + docs/modules/ROOT/pages/api/governance.adoc | 1203 ++++++++++++++++- docs/modules/ROOT/pages/governance.adoc | 427 ------ .../ROOT/pages/governance/governor.adoc | 371 +++++ .../ROOT/pages/governance/timelock.adoc | 192 +++ docs/modules/ROOT/pages/governance/votes.adoc | 229 ++++ .../governance/src/governor/interface.cairo | 2 +- 8 files changed, 1985 insertions(+), 447 deletions(-) delete mode 100644 docs/modules/ROOT/pages/governance.adoc create mode 100644 docs/modules/ROOT/pages/governance/governor.adoc create mode 100644 docs/modules/ROOT/pages/governance/timelock.adoc create mode 100644 docs/modules/ROOT/pages/governance/votes.adoc 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..4873c91fc 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -10,11 +10,1180 @@ :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] = 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} +-- +0x1 +-- + +[.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 the proposal is created and 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 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] +[[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] + +[.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: {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 the proposal is created and 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 `COUNTING_MODE()` 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 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 execution is +performed (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_queueing]] +==== `[.contract-item-name]#++proposal_needs_queueing++#++(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 the proposal is created and 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. + +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` should 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` should 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. + +[.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, that it has not been cast yet, retrieve +voting weight using `get_votes` and call 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. More extensions may be added in future releases. + == Timelock In a governance system, `TimelockControllerComponent` is in charge of introducing a delay between a proposal and its execution. @@ -276,21 +1445,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 +1505,7 @@ Component that implements <> and enables the implementing -- [#TimelockControllerComponent-Functions] -==== Functions +==== Embeddable functions [.contract-item] [[TimelockControllerComponent-is_operation]] @@ -774,7 +1943,7 @@ 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]] 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..6ebe53500 --- /dev/null +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -0,0 +1,371 @@ += 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 small 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) execution mechanism should be used. Each +of these aspects is customizable by writing your own extensions, or more easily choosing one from the library. + +**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 as an initializer +parameter the address of the token. + +**For 2)** we will use GovernorVotesQuorumFraction which works together with the {ivotes} instance to define quorum as a +percentage of the total supply at the block 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 to execute proposals directly through the governor. + +NOTE: Another option is GovernorTimelockExecution, which requires a timelock contract to be set up +and then uses it to execute proposals. + +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 setting 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/interface.cairo b/packages/governance/src/governor/interface.cairo index 6bc7bad1b..f651b82d9 100644 --- a/packages/governance/src/governor/interface.cairo +++ b/packages/governance/src/governor/interface.cairo @@ -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; From 90101511fc5b3115ce58ee29fc94c7a648c3be4f Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Thu, 28 Nov 2024 15:33:28 +0100 Subject: [PATCH 2/9] feat: add extensions API References --- docs/modules/ROOT/pages/api/governance.adoc | 796 +++++++++++++++++- .../extensions/governor_core_execution.cairo | 8 + .../extensions/governor_counting_simple.cairo | 2 + .../governor_timelock_execution.cairo | 2 +- .../governor_votes_quorum_fraction.cairo | 2 + .../governance/src/governor/interface.cairo | 2 +- 6 files changed, 795 insertions(+), 17 deletions(-) diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index 4873c91fc..c7a11becb 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -12,6 +12,8 @@ :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] = Governance @@ -380,6 +382,7 @@ Emitted when a vote is cast with params. :ProposalCanceled: xref:GovernorComponent-ProposalCanceled[ProposalCanceled] :VoteCast: xref:GovernorComponent-VoteCast[VoteCast] :VoteCastWithParams: xref:GovernorComponent-VoteCastWithParams[VoteCastWithParams] +:component-extensions: xref:#governor_extensions[component extensions] [.contract] [[GovernorComponent]] @@ -392,6 +395,10 @@ 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 them. They can be implemented locally on the contract, or through the provided library +{component-extensions}. + NOTE: {src5-component-required-note} [.contract-index#GovernorComponent-Extensions-Traits-Traits] @@ -1182,7 +1189,766 @@ Emitted when a vote is cast with params. 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. More extensions may be added in future releases. +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. + +[.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 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 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 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. + +[.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 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. + +[.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 proposal (in addition to the voting duration). + +NOTE: The Governor needs the 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. + +[.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 executionof 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 @@ -1908,7 +2674,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. @@ -1927,7 +2693,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. @@ -1947,13 +2713,13 @@ may compromise the internal vote accounting. [.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. @@ -1963,7 +2729,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. @@ -1977,13 +2743,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`. @@ -1993,7 +2759,7 @@ 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. @@ -2013,13 +2779,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. @@ -2027,7 +2793,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. @@ -2040,19 +2806,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/packages/governance/src/governor/extensions/governor_core_execution.cairo b/packages/governance/src/governor/extensions/governor_core_execution.cairo index 9ccddf98a..c5a9fa857 100644 --- a/packages/governance/src/governor/extensions/governor_core_execution.cairo +++ b/packages/governance/src/governor/extensions/governor_core_execution.cairo @@ -43,11 +43,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 +65,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 +77,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_timelock_execution.cairo b/packages/governance/src/governor/extensions/governor_timelock_execution.cairo index 39fd848ad..251f4e2a1 100644 --- a/packages/governance/src/governor/extensions/governor_timelock_execution.cairo +++ b/packages/governance/src/governor/extensions/governor_timelock_execution.cairo @@ -212,7 +212,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/interface.cairo b/packages/governance/src/governor/interface.cairo index f651b82d9..71a800e78 100644 --- a/packages/governance/src/governor/interface.cairo +++ b/packages/governance/src/governor/interface.cairo @@ -10,7 +10,7 @@ pub const IGOVERNOR_ID: felt252 = 0x1f; // TODO: Update this value. #[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. From 2d056d76001f562ec5ec22d6c8132757ab2a1179 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 29 Nov 2024 13:08:12 +0100 Subject: [PATCH 3/9] Update docs/modules/ROOT/pages/governance/governor.adoc Co-authored-by: Andrew Fleming --- docs/modules/ROOT/pages/governance/governor.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc index 6ebe53500..b29275151 100644 --- a/docs/modules/ROOT/pages/governance/governor.adoc +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -16,7 +16,7 @@ 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 small traits. You will find the most common requirements out of the box, +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 From b980a24884bde2b764a2d595701aed80e365e0ae Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 29 Nov 2024 13:08:45 +0100 Subject: [PATCH 4/9] Update docs/modules/ROOT/pages/governance/governor.adoc Co-authored-by: Andrew Fleming --- docs/modules/ROOT/pages/governance/governor.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc index b29275151..e251f80af 100644 --- a/docs/modules/ROOT/pages/governance/governor.adoc +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -40,7 +40,7 @@ block numbers instead. 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) execution mechanism should be used. Each +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. **For 1)** we will use the GovernorVotes extension, which hooks to an {ivotes} instance to determine the voting power From cba6f8ccffa5b8b971676ad6360b00e91476e358 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 29 Nov 2024 14:05:41 +0100 Subject: [PATCH 5/9] feat: apply review updates --- docs/modules/ROOT/pages/api/governance.adoc | 157 +++++++++++------- .../ROOT/pages/governance/governor.adoc | 47 ++++-- .../extensions/governor_core_execution.cairo | 4 + .../extensions/governor_settings.cairo | 2 +- .../governor_timelock_execution.cairo | 6 +- .../governance/src/governor/governor.cairo | 14 +- 6 files changed, 146 insertions(+), 84 deletions(-) diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index c7a11becb..ffcba8dd9 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -14,6 +14,8 @@ :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 @@ -95,13 +97,13 @@ Interface of a governor contract. [[IGovernor-name]] ==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external# -Name of the governor instance (used in building the SNIP-12 domain separator). +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). +Version of the governor instance (used in building {SNIP-12} domain separator). [.contract-item] [[IGovernor-COUNTING_MODE]] @@ -117,8 +119,10 @@ There are 2 standard keys: `support` and `quorum`. `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. @@ -182,8 +186,8 @@ Whether a proposal needs to be queued before execution. This indicates if the pr [[IGovernor-voting_delay]] ==== `[.contract-item-name]#++voting_delay++#++() → u64++` [.item-kind]#external# -Delay between the proposal is created and the vote starts. The unit this duration is expressed in -depends on the clock (see ERC-6372) this contract uses. +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. @@ -191,8 +195,8 @@ This can be increased to leave time for users to buy voting power, or delegate i [[IGovernor-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. +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. @@ -214,7 +218,7 @@ timepoint. [[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. +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. @@ -223,13 +227,13 @@ balance from one (or multiple) `ERC20Votes` tokens. [[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. +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. +Returns whether an `account` has cast a vote on a proposal. [.contract-item] [[IGovernor-propose]] @@ -291,7 +295,7 @@ Returns the weight of the vote. [[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. +Cast a vote on a proposal with a `reason`. Returns the weight of the vote. @@ -396,7 +400,7 @@ 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 them. They can be implemented locally on the contract, or through the provided library +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} @@ -525,7 +529,7 @@ NOTE: {src5-component-required-note} [[GovernorComponent-GovernorSettingsTrait-voting_delay]] ==== `[.contract-item-name]#++voting_delay++#++(self: @ContractState) → u64++` [.item-kind]#extension# -Must return the delay, in number of timepoints, between the proposal is created and the vote starts. This can be +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. @@ -552,7 +556,8 @@ Must return the minimum number of votes required for a proposal to succeed. ==== `[.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 `COUNTING_MODE()` for more details. +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]] @@ -584,7 +589,7 @@ Must return whether a proposal has succeeded or not. [[GovernorComponent-GovernorVotesTrait-clock]] ==== `[.contract-item-name]#++clock++#++(self: @ContractState) → u64++` [.item-kind]#extension# -Must return the current timepoint according to the mode the governor is operating in. +Must return the current timepoint according to the clock mode the governor is operating in. NOTE: For now, only timestamp is supported. @@ -610,13 +615,13 @@ 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. +- `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]] @@ -639,8 +644,7 @@ executor. [[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 execution is -performed (for example adding a vault/timelock). +Execution mechanism. Can be used to modify the way operations are executed (for example adding a vault/timelock). [.contract-item] [[GovernorComponent-GovernorExecutionTrait-queue_operations]] @@ -656,8 +660,8 @@ value is 0, the core will consider queueing did not succeed, and the public `que function will revert. [.contract-item] -[[GovernorComponent-GovernorExecutionTrait-proposal_needs_queueing]] -==== `[.contract-item-name]#++proposal_needs_queueing++#++(self: @ContractState) → bool++` [.item-kind]#internal# +[[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. @@ -675,13 +679,13 @@ performed (for example adding a vault/timelock). [[GovernorComponent-name]] ==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external# -Name of the governor instance (used in building the SNIP-12 domain separator). +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). +Version of the governor instance (used in building {SNIP-12} domain separator). [.contract-item] [[GovernorComponent-COUNTING_MODE]] @@ -697,8 +701,10 @@ There are 2 standard keys: `support` and `quorum`. `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. @@ -762,8 +768,8 @@ Whether a proposal needs to be queued before execution. This indicates if the pr [[GovernorComponent-voting_delay]] ==== `[.contract-item-name]#++voting_delay++#++() → u64++` [.item-kind]#external# -Delay between the proposal is created and the vote starts. The unit this duration is expressed in -depends on the clock (see ERC-6372) this contract uses. +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. @@ -772,7 +778,7 @@ This can be increased to leave time for users to buy voting power, or delegate i ==== `[.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. +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. @@ -794,7 +800,7 @@ timepoint. [[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. +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. @@ -815,25 +821,25 @@ Returns whether an account has cast a vote on a proposal. [[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. +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`. +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). +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: +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. +- 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. @@ -886,6 +892,7 @@ 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. @@ -932,14 +939,14 @@ Emits either: [[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. +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` should be valid for the message hash. +- `signature` must be valid for the message hash. Emits a {VoteCast} event. @@ -955,7 +962,7 @@ 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` should be valid for the message hash. +- `signature` must be valid for the message hash. Emits either: @@ -1016,6 +1023,7 @@ 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. @@ -1028,7 +1036,8 @@ it. This includes: Returns the proposal id computed from the given parameters. -The proposal id is computed as a pedersen hash of: +The proposal id is computed as a Pedersen hash of: + - The array of calls being proposed - The description hash @@ -1103,6 +1112,10 @@ Internal wrapper for `GovernorProposeTrait::proposal_threshold`. 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# @@ -1123,7 +1136,7 @@ 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. [.contract-item] [[GovernorComponent-_count_vote]] @@ -1135,12 +1148,14 @@ Internal wrapper for `GovernorCountingTrait::count_vote`. [[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. +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. - {VoteCastWithParams} event otherwise. @@ -1226,6 +1241,10 @@ xref:#GovernorTimelockExecutionComponent[GovernorTimelockExecutionComponent]. 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# @@ -1334,7 +1353,7 @@ In this implementation, the For votes must be strictly greater than Against vote use openzeppelin_governance::governor::extensions::GovernorSettingsComponent; ``` -Extension of {GovernorComponent} for settings updatable through governance. +Extension of {GovernorComponent} for settings that are updatable through governance. [.contract-index] .Extension traits implementations @@ -1389,7 +1408,7 @@ Returns the delay, between when a proposal is created and when voting starts. [[GovernorSettingsComponent-voting_period]] ==== `[.contract-item-name]#++voting_period++#++(self: @ContractState) → u64++` [.item-kind]#internal# -Returns the period, during which votes can be cast. +Returns the time period, during which votes can be cast. [.contract-item] [[GovernorSettingsComponent-proposal_threshold]] @@ -1571,7 +1590,7 @@ extension. [[GovernorVotesComponent-clock]] ==== `[.contract-item-name]#++clock++#++(self: @ContractState) → u64++` [.item-kind]#internal# -Returns the current timepoint according to the mode the contract is operating in. +Returns the current timepoint according to the time mode the contract is operating in. In this implementation, returns the current block timestamp. @@ -1610,6 +1629,12 @@ Returns the votes token that voting power is sourced from. 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] @@ -1675,7 +1700,7 @@ Returns a percentage of the votes token total supply at a given `timepoint` in t [[GovernorVotesQuorumFractionComponent-clock]] ==== `[.contract-item-name]#++clock++#++(self: @ContractState) → u64++` [.item-kind]#internal# -Returns the current timepoint according to the mode the contract is operating in. +Returns the current timepoint according to the time mode the contract is operating in. In this implementation, returns the current block timestamp. @@ -1759,6 +1784,8 @@ May emit a {QuorumNumeratorUpdated} 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] @@ -1770,9 +1797,9 @@ use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComp 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 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. +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 @@ -1836,6 +1863,10 @@ voters, effectively executing a Denial of Service attack. 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# @@ -1856,7 +1887,7 @@ Runs the already queued proposal through the timelock. Queue a proposal to the timelock. -Returns the eta for the executionof the queued proposal. +Returns the eta for the execution of the queued proposal. [.contract-item] [[GovernorTimelockExecutionComponent-proposal_needs_queuing]] @@ -2611,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]] @@ -2761,7 +2792,7 @@ May emit one or two {DelegateVotesChanged} events. [[VotesComponent-delegate_by_sig]] ==== `[.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: diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc index e251f80af..3a84a5c98 100644 --- a/docs/modules/ROOT/pages/governance/governor.adoc +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -39,31 +39,48 @@ 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. +still need to choose: -**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 as an initializer -parameter the address of the token. +1) how voting power is determined, -**For 2)** we will use GovernorVotesQuorumFraction which works together with the {ivotes} instance to define quorum as a -percentage of the total supply at the block a proposal’s voting power is retrieved. This requires an initializer +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, +**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 to execute proposals directly through the governor. +**For 4)** we will use {GovernorCoreExecution}, an extension that allows proposal execution directly through the governor. -NOTE: Another option is GovernorTimelockExecution, which requires a timelock contract to be set up -and then uses it to execute proposals. +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 setting constant values. +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. diff --git a/packages/governance/src/governor/extensions/governor_core_execution.cairo b/packages/governance/src/governor/extensions/governor_core_execution.cairo index c5a9fa857..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 { 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 251f4e2a1..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 { 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. From 17fc3e661d85da727c295c9506bdaffbe279a47a Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 29 Nov 2024 14:54:38 +0100 Subject: [PATCH 6/9] feat: add interface id --- docs/modules/ROOT/pages/api/governance.adoc | 2 +- .../governance/src/governor/interface.cairo | 73 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index ffcba8dd9..87f8151e4 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -43,7 +43,7 @@ Interface of a governor contract. [.contract-index] .{inner-src5} -- -0x1 +0x1100a1f8546595b5bd75a6cd8fcc5b015370655e66f275963321c5cd0357ac9 -- [.contract-index] diff --git a/packages/governance/src/governor/interface.cairo b/packages/governance/src/governor/interface.cairo index 71a800e78..a7adf9310 100644 --- a/packages/governance/src/governor/interface.cairo +++ b/packages/governance/src/governor/interface.cairo @@ -4,7 +4,7 @@ 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] @@ -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); +} From 0b9848da6921f5cb1f986439bb300ce2f2dcfbef Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 2 Dec 2024 14:11:25 +0100 Subject: [PATCH 7/9] feat: apply review updates --- docs/modules/ROOT/pages/api/governance.adoc | 7 +- .../governor_timelock_execution.cairo | 3 +- .../governance/src/governor/interface.cairo | 72 ------------------- 3 files changed, 6 insertions(+), 76 deletions(-) diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index 87f8151e4..cf9484d49 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -319,7 +319,7 @@ Returns the weight of the vote. [[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. +Cast a vote on a proposal with a reason and additional encoded parameters using the ``voter``'s signature. Returns the weight of the vote. @@ -954,7 +954,7 @@ Emits a {VoteCast} event. [[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 +Cast a vote with a `reason` and additional serialized `params` using the ``voter``'s signature. Requirements: @@ -1953,7 +1953,8 @@ Ensures the caller is the executor (the timelock controller in this case). [[GovernorTimelockExecutionComponent-timelock_salt]] ==== `[.contract-item-name]#++timelock_salt++#++(self: @ContractState, description_hash: felt252) → felt252++` [.item-kind]#internal# -Computes the `TimelockController` operation salt. +Computes the `TimelockController` operation salt as the XOR of +the governor address and `description_hash`. It is computed with the governor address itself to avoid collisions across governor instances using the same timelock. diff --git a/packages/governance/src/governor/extensions/governor_timelock_execution.cairo b/packages/governance/src/governor/extensions/governor_timelock_execution.cairo index bb72981a4..ca7e2345d 100644 --- a/packages/governance/src/governor/extensions/governor_timelock_execution.cairo +++ b/packages/governance/src/governor/extensions/governor_timelock_execution.cairo @@ -276,7 +276,8 @@ pub mod GovernorTimelockExecutionComponent { governor_component.assert_only_governance(); } - /// Computes the `TimelockController` operation salt. + /// Computes the `TimelockController` operation salt as the XOR of + /// the governor address and `description_hash`. /// /// It is computed with the governor address itself to avoid collisions across /// governor instances using the same timelock. diff --git a/packages/governance/src/governor/interface.cairo b/packages/governance/src/governor/interface.cairo index a7adf9310..8d8f5cfc7 100644 --- a/packages/governance/src/governor/interface.cairo +++ b/packages/governance/src/governor/interface.cairo @@ -10,7 +10,6 @@ pub const IGOVERNOR_ID: felt252 = 0x1100a1f8546595b5bd75a6cd8fcc5b015370655e66f2 #[starknet::interface] pub trait IERC6372 { /// Clock used for flagging checkpoints. - /// Can be overridden to implement block number based checkpoints (and voting). fn clock(self: @TState) -> u64; /// Description of the clock. @@ -215,74 +214,3 @@ 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); -} From ef18c54ec39dd0931b024809e1e648b13e46b264 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 2 Dec 2024 20:39:37 +0100 Subject: [PATCH 8/9] feat: add interface --- .../ROOT/pages/governance/governor.adoc | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc index 3a84a5c98..434941a3d 100644 --- a/docs/modules/ROOT/pages/governance/governor.adoc +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -386,3 +386,65 @@ pub mod MyTimelockedGovernor { } } ---- + +== Interface + +This is the full interface of the `Governor` implementation: +[source,cairo] +---- +#[starknet::interface] +pub trait IGovernor { + fn name(self: @TState) -> felt252; + fn version(self: @TState) -> felt252; + fn COUNTING_MODE(self: @TState) -> ByteArray; + fn hash_proposal(self: @TState, calls: Span, description_hash: felt252) -> felt252; + fn state(self: @TState, proposal_id: felt252) -> ProposalState; + fn proposal_threshold(self: @TState) -> u256; + fn proposal_snapshot(self: @TState, proposal_id: felt252) -> u64; + fn proposal_deadline(self: @TState, proposal_id: felt252) -> u64; + fn proposal_proposer(self: @TState, proposal_id: felt252) -> ContractAddress; + fn proposal_eta(self: @TState, proposal_id: felt252) -> u64; + fn proposal_needs_queuing(self: @TState, proposal_id: felt252) -> bool; + fn voting_delay(self: @TState) -> u64; + fn voting_period(self: @TState) -> u64; + fn quorum(self: @TState, timepoint: u64) -> u256; + fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; + fn get_votes_with_params( + self: @TState, account: ContractAddress, timepoint: u64, params: Span + ) -> u256; + fn has_voted(self: @TState, proposal_id: felt252, account: ContractAddress) -> bool; + fn propose(ref self: TState, calls: Span, description: ByteArray) -> felt252; + fn queue(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn execute(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn cancel(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn cast_vote(ref self: TState, proposal_id: felt252, support: u8) -> u256; + fn cast_vote_with_reason( + ref self: TState, proposal_id: felt252, support: u8, reason: ByteArray + ) -> u256; + fn cast_vote_with_reason_and_params( + ref self: TState, + proposal_id: felt252, + support: u8, + reason: ByteArray, + params: Span + ) -> u256; + fn cast_vote_by_sig( + ref self: TState, + proposal_id: felt252, + support: u8, + voter: ContractAddress, + signature: Span + ) -> u256; + fn cast_vote_with_reason_and_params_by_sig( + ref self: TState, + proposal_id: felt252, + support: u8, + voter: ContractAddress, + reason: ByteArray, + params: Span, + signature: Span + ) -> u256; + fn nonces(self: @TState, voter: ContractAddress) -> felt252; + fn relay(ref self: TState, call: Call); +} +---- From 43b4170f14638ce1503b07a0a7dc1a4a6cd407bc Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Tue, 3 Dec 2024 14:36:19 +0100 Subject: [PATCH 9/9] feat: apply review updates --- docs/modules/ROOT/pages/api/governance.adoc | 2 +- docs/modules/ROOT/pages/governance/governor.adoc | 5 ++--- .../governor/extensions/governor_votes_quorum_fraction.cairo | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index cf9484d49..0641d458f 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -1694,7 +1694,7 @@ Extension of {GovernorComponent} for voting weight extraction from a token with [[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. +It is computed as a percentage of the votes token total supply at a given `timepoint` in the past. [.contract-item] [[GovernorVotesQuorumFractionComponent-clock]] diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc index 434941a3d..28bc08a37 100644 --- a/docs/modules/ROOT/pages/governance/governor.adoc +++ b/docs/modules/ROOT/pages/governance/governor.adoc @@ -66,9 +66,8 @@ This module requires the address of the token to be passed as an argument to the **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%). +parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%. Since the quorum denominator +is 1000 for precision, we initialize the module with a numerator of 40, resulting in a 4% quorum (40/1000 = 0.04 or 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. 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 3bde2c673..b0f0213e2 100644 --- a/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo +++ b/packages/governance/src/governor/extensions/governor_votes_quorum_fraction.cairo @@ -55,7 +55,8 @@ pub mod GovernorVotesQuorumFractionComponent { > of GovernorComponent::GovernorQuorumTrait { /// See `GovernorComponent::GovernorQuorumTrait::quorum`. /// - /// Returns a percentage of the votes token total supply at a given timepoint in the past. + /// It is computed as 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);