From e28b98768b5b73c73d929e17fd53f421303cd048 Mon Sep 17 00:00:00 2001
From: immrsd <103599616+immrsd@users.noreply.github.com>
Date: Wed, 18 Dec 2024 14:26:41 +0100
Subject: [PATCH] Multisig Docs (#1254)
* Add Multisig API references
* Fix Votes broken link
* Add Multisig doc page
* Fix RoleGranted event link in doc
* Rename refs to events from Votes component to be explicit
* Add newlines after "Requirements:"
* Improve description of the is_confirmed fn
* Add requirements and events to Multisig component function references
* Fix typo
* Fix doc line regarding QuorumUpdated event
* Fix event links
* Fix functions order in doc
* Fix capitalisation
* Add import section
* Add description for Multisig module
* Add newlines where necessary
* Improve multisig doc indices
* Add detailed explanation of ID computation of a multisig tx
* Add detailed explanation of ID computation of a timelock operation
* Fix order of Multisig functions
* Add descriptions for possible states of a Timelock operation
* Add multisig tx states description
* Fix minor issues in governance doc
* Improve doc for Timelock cancel function
* Fix description of Waiting state of a Timelock tx
* Fix links
* Fix doc review issues
* Add corresponding newlines in in-code Multisig doc
---
docs/modules/ROOT/nav.adoc | 1 +
docs/modules/ROOT/pages/api/governance.adoc | 902 +++++++++++++++++-
.../ROOT/pages/governance/governor.adoc | 1 +
.../ROOT/pages/governance/multisig.adoc | 154 +++
.../ROOT/pages/governance/timelock.adoc | 18 +-
docs/modules/ROOT/pages/governance/votes.adoc | 6 +-
.../governance/src/multisig/multisig.cairo | 75 +-
.../src/timelock/timelock_controller.cairo | 9 +-
8 files changed, 1085 insertions(+), 81 deletions(-)
create mode 100644 docs/modules/ROOT/pages/governance/multisig.adoc
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index f8c64e604..f464a2239 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -22,6 +22,7 @@
** Governance
*** xref:/governance/governor.adoc[Governor]
+*** xref:/governance/multisig.adoc[Multisig]
*** xref:/governance/timelock.adoc[Timelock Controller]
*** xref:/governance/votes.adoc[Votes]
*** xref:/api/governance.adoc[API Reference]
diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc
index ed0a37486..c92bef16e 100644
--- a/docs/modules/ROOT/pages/api/governance.adoc
+++ b/docs/modules/ROOT/pages/api/governance.adoc
@@ -1,12 +1,7 @@
:github-icon: pass:[]
-:CallScheduled: xref:ITimelock-CallScheduled[CallScheduled]
-:CallExecuted: xref:ITimelock-CallExecuted[CallExecuted]
-:CallSalt: xref:ITimelock-CallSalt[CallSalt]
-:CallCancelled: xref:ITimelock-CallCancelled[CallCancelled]
-:MinDelayChanged: xref:ITimelock-MinDelayChanged[MinDelayChanged]
-:RoleGranted: xref:api/access.adoc#IAccessControl-RoleGranted[IAccessControl::RoleGranted]
-:DelegateChanged: xref:VotesComponent-DelegateChanged[DelegateChanged]
-:DelegateVotesChanged: xref:VotesComponent-DelegateVotesChanged[DelegateVotesChanged]
+:IAccessControl-RoleGranted: xref:api/access.adoc#IAccessControl-RoleGranted[IAccessControl::RoleGranted]
+:VotesComponent-DelegateChanged: xref:VotesComponent-DelegateChanged[DelegateChanged]
+:VotesComponent-DelegateVotesChanged: xref:VotesComponent-DelegateVotesChanged[DelegateVotesChanged]
:VotingUnitsTrait: xref:VotingUnitsTrait[VotingUnitsTrait]
:VotesComponent: xref:VotesComponent[VotesComponent]
:IVotes: xref:IVotes[IVotes]
@@ -1982,6 +1977,803 @@ Emits a {TimelockUpdated} event.
Emitted when the timelock controller is updated.
+== Multisig
+
+A Multisig module enhances security and decentralization by requiring multiple signers to
+approve and execute transactions. Features include configurable quorum, signer management,
+and self-administration, ensuring collective decision-making and transparency for critical
+operations.
+
+[.contract]
+[[IMultisig]]
+=== `++IMultisig++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0/packages/governance/src/multisig/interface.cairo[{github-icon},role=heading-link]
+
+:IMultisig-CallSalt: xref:IMultisig-CallSalt[CallSalt]
+:IMultisig-SignerAdded: xref:IMultisig-SignerAdded[SignerAdded]
+:IMultisig-SignerRemoved: xref:IMultisig-SignerRemoved[SignerRemoved]
+:IMultisig-QuorumUpdated: xref:IMultisig-QuorumUpdated[QuorumUpdated]
+:IMultisig-TransactionSubmitted: xref:IMultisig-TransactionSubmitted[TransactionSubmitted]
+:IMultisig-TransactionConfirmed: xref:IMultisig-TransactionConfirmed[TransactionConfirmed]
+:IMultisig-ConfirmationRevoked: xref:IMultisig-ConfirmationRevoked[ConfirmationRevoked]
+:IMultisig-TransactionExecuted: xref:IMultisig-TransactionExecuted[TransactionExecuted]
+
+[.hljs-theme-dark]
+```cairo
+use openzeppelin_governance::multisig::interface::IMultisig;
+```
+
+Interface of a multisig contract.
+
+[.contract-index]
+.Functions
+--
+* xref:#IMultisig-get_quorum[`++get_quorum()++`]
+* xref:#IMultisig-is_signer[`++is_signer(signer)++`]
+* xref:#IMultisig-get_signers[`++get_signers()++`]
+* xref:#IMultisig-is_confirmed[`++is_confirmed(id)++`]
+* xref:#IMultisig-is_confirmed_by[`++is_confirmed_by(id, signer)++`]
+* xref:#IMultisig-is_executed[`++is_executed(id)++`]
+* xref:#IMultisig-get_submitted_block[`++get_submitted_block(id)++`]
+* xref:#IMultisig-get_transaction_state[`++get_transaction_state(id)++`]
+* xref:#IMultisig-get_transaction_confirmations[`++get_transaction_confirmations(id)++`]
+* xref:#IMultisig-hash_transaction[`++hash_transaction(to, selector, calldata, salt)++`]
+* xref:#IMultisig-hash_transaction_batch[`++hash_transaction_batch(calls, salt)++`]
+* xref:#IMultisig-add_signers[`++add_signers(new_quorum, signers_to_add)++`]
+* xref:#IMultisig-remove_signers[`++remove_signers(new_quorum, signers_to_remove)++`]
+* xref:#IMultisig-replace_signer[`++replace_signer(signer_to_remove, signer_to_add)++`]
+* xref:#IMultisig-change_quorum[`++change_quorum(new_quorum)++`]
+* xref:#IMultisig-submit_transaction[`++submit_transaction(to, selector, calldata, salt)++`]
+* xref:#IMultisig-submit_transaction_batch[`++submit_transaction_batch(calls, salt)++`]
+* xref:#IMultisig-confirm_transaction[`++confirm_transaction(id)++`]
+* xref:#IMultisig-revoke_confirmation[`++revoke_confirmation(id)++`]
+* xref:#IMultisig-execute_transaction[`++execute_transaction(to, selector, calldata, salt)++`]
+* xref:#IMultisig-execute_transaction_batch[`++execute_transaction_batch(calls, salt)++`]
+--
+
+[.contract-index]
+.Events
+--
+* xref:#IMultisig-SignerAdded[`++SignerAdded(signer)++`]
+* xref:#IMultisig-SignerRemoved[`++SignerRemoved(signer)++`]
+* xref:#IMultisig-QuorumUpdated[`++QuorumUpdated(old_quorum, new_quorum)++`]
+* xref:#IMultisig-TransactionSubmitted[`++TransactionSubmitted(id, signer)++`]
+* xref:#IMultisig-TransactionConfirmed[`++TransactionConfirmed(id, signer)++`]
+* xref:#IMultisig-ConfirmationRevoked[`++ConfirmationRevoked(id, signer)++`]
+* xref:#IMultisig-TransactionExecuted[`++TransactionExecuted(id)++`]
+* xref:#IMultisig-CallSalt[`++CallSalt(id, salt)++`]
+--
+
+[#IMultisig-Functions]
+==== Functions
+
+[.contract-item]
+[[IMultisig-get_quorum]]
+==== `[.contract-item-name]#++get_quorum++#++() → u32++` [.item-kind]#external#
+
+Returns the current quorum value. The quorum is the minimum number of confirmations required to approve a transaction.
+
+[.contract-item]
+[[IMultisig-is_signer]]
+==== `[.contract-item-name]#++is_signer++#++(signer: ContractAddress) → bool++` [.item-kind]#external#
+
+Returns whether the given `signer` is registered. Only registered signers can submit, confirm, or execute transactions.
+
+[.contract-item]
+[[IMultisig-get_signers]]
+==== `[.contract-item-name]#++get_signers++#++() → Span++` [.item-kind]#external#
+
+Returns the list of all current signers.
+
+[.contract-item]
+[[IMultisig-is_confirmed]]
+==== `[.contract-item-name]#++is_confirmed++#++(id: TransactionID) → bool++` [.item-kind]#external#
+
+Returns whether the transaction with the given `id` has been confirmed.
+
+[.contract-item]
+[[IMultisig-is_confirmed_by]]
+==== `[.contract-item-name]#++is_confirmed_by++#++(id: TransactionID, signer: ContractAddress) → bool++` [.item-kind]#external#
+
+Returns whether the transaction with the given `id` has been confirmed by the specified `signer`.
+
+[.contract-item]
+[[IMultisig-is_executed]]
+==== `[.contract-item-name]#++is_executed++#++(id: TransactionID) → bool++` [.item-kind]#external#
+
+Returns whether the transaction with the given `id` has been executed.
+
+[.contract-item]
+[[IMultisig-get_submitted_block]]
+==== `[.contract-item-name]#++get_submitted_block++#++(id: TransactionID) → u64++` [.item-kind]#external#
+
+Returns the block number when the transaction with the given `id` was submitted.
+
+[.contract-item]
+[[IMultisig-get_transaction_state]]
+==== `[.contract-item-name]#++get_transaction_state++#++(id: TransactionID) → TransactionState++` [.item-kind]#external#
+
+Returns the current state of the transaction with the given `id`.
+
+[.contract-item]
+[[IMultisig-get_transaction_confirmations]]
+==== `[.contract-item-name]#++get_transaction_confirmations++#++(id: TransactionID) → u32++` [.item-kind]#external#
+
+Returns the number of confirmations from registered signers for the transaction with the specified `id`.
+
+[.contract-item]
+[[IMultisig-hash_transaction]]
+==== `[.contract-item-name]#++hash_transaction++#++(to: ContractAddress, selector: felt252, calldata: Span, salt: felt252) → TransactionID++` [.item-kind]#external#
+
+Returns the computed identifier of a transaction containing a single call.
+
+[.contract-item]
+[[IMultisig-hash_transaction_batch]]
+==== `[.contract-item-name]#++hash_transaction_batch++#++(calls: Span, salt: felt252) → TransactionID++` [.item-kind]#external#
+
+Returns the computed identifier of a transaction containing a batch of calls.
+
+[.contract-item]
+[[IMultisig-add_signers]]
+==== `[.contract-item-name]#++add_signers++#++(new_quorum: u32, signers_to_add: Span)++` [.item-kind]#external#
+
+Adds new signers and updates the quorum.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `new_quorum` must be less than or equal to the total number of signers after addition.
+
+Emits a {IMultisig-SignerAdded} event for each signer added.
+
+Emits a {IMultisig-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[IMultisig-remove_signers]]
+==== `[.contract-item-name]#++remove_signers++#++(new_quorum: u32, signers_to_remove: Span)++` [.item-kind]#external#
+
+Removes signers and updates the quorum.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `new_quorum` must be less than or equal to the total number of signers after removal.
+
+Emits a {IMultisig-SignerRemoved} event for each signer removed.
+
+Emits a {IMultisig-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[IMultisig-replace_signer]]
+==== `[.contract-item-name]#++replace_signer++#++(signer_to_remove: ContractAddress, signer_to_add: ContractAddress)++` [.item-kind]#external#
+
+Replaces an existing signer with a new signer.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `signer_to_remove` must be an existing signer.
+- `signer_to_add` must not be an existing signer.
+
+Emits a {IMultisig-SignerRemoved} event for the removed signer.
+
+Emits a {IMultisig-SignerAdded} event for the new signer.
+
+[.contract-item]
+[[IMultisig-change_quorum]]
+==== `[.contract-item-name]#++change_quorum++#++(new_quorum: u32)++` [.item-kind]#external#
+
+Updates the quorum value to `new_quorum` if it differs from the current quorum.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `new_quorum` must be non-zero.
+- `new_quorum` must be less than or equal to the total number of signers.
+
+Emits a {IMultisig-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[IMultisig-submit_transaction]]
+==== `[.contract-item-name]#++submit_transaction++#++(to: ContractAddress, selector: felt252, calldata: Span, salt: felt252) → TransactionID++` [.item-kind]#external#
+
+Submits a new transaction for confirmation.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must not have been submitted before.
+
+Emits a {IMultisig-TransactionSubmitted} event.
+
+Emits a {IMultisig-CallSalt} event if `salt` is not zero.
+
+[.contract-item]
+[[IMultisig-submit_transaction_batch]]
+==== `[.contract-item-name]#++submit_transaction_batch++#++(calls: Span, salt: felt252) → TransactionID++` [.item-kind]#external#
+
+Submits a new batch transaction for confirmation.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must not have been submitted before.
+
+Emits a {IMultisig-TransactionSubmitted} event.
+
+Emits a {IMultisig-CallSalt} event if `salt` is not zero.
+
+[.contract-item]
+[[IMultisig-confirm_transaction]]
+==== `[.contract-item-name]#++confirm_transaction++#++(id: TransactionID)++` [.item-kind]#external#
+
+Confirms a transaction with the given `id`.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must exist and not be executed.
+- The caller must not have already confirmed the transaction.
+
+Emits a {IMultisig-TransactionConfirmed} event.
+
+[.contract-item]
+[[IMultisig-revoke_confirmation]]
+==== `[.contract-item-name]#++revoke_confirmation++#++(id: TransactionID)++` [.item-kind]#external#
+
+Revokes a previous confirmation for a transaction with the given `id`.
+
+Requirements:
+
+- The transaction must exist and not be executed.
+- The caller must have previously confirmed the transaction.
+
+Emits a {IMultisig-ConfirmationRevoked} event.
+
+[.contract-item]
+[[IMultisig-execute_transaction]]
+==== `[.contract-item-name]#++execute_transaction++#++(to: ContractAddress, selector: felt252, calldata: Span, salt: felt252)++` [.item-kind]#external#
+
+Executes a confirmed transaction.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must be confirmed and not yet executed.
+
+Emits a {IMultisig-TransactionExecuted} event.
+
+[.contract-item]
+[[IMultisig-execute_transaction_batch]]
+==== `[.contract-item-name]#++execute_transaction_batch++#++(calls: Span, salt: felt252)++` [.item-kind]#external#
+
+Executes a confirmed batch transaction.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must be confirmed and not yet executed.
+
+Emits a {IMultisig-TransactionExecuted} event.
+
+[#IMultisig-Events]
+==== Events
+
+[.contract-item]
+[[IMultisig-SignerAdded]]
+==== `[.contract-item-name]#++SignerAdded++#++(signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a new `signer` is added.
+
+[.contract-item]
+[[IMultisig-SignerRemoved]]
+==== `[.contract-item-name]#++SignerRemoved++#++(signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a `signer` is removed.
+
+[.contract-item]
+[[IMultisig-QuorumUpdated]]
+==== `[.contract-item-name]#++QuorumUpdated++#++(old_quorum: u32, new_quorum: u32)++` [.item-kind]#event#
+
+Emitted when the `quorum` value is updated.
+
+[.contract-item]
+[[IMultisig-TransactionSubmitted]]
+==== `[.contract-item-name]#++TransactionSubmitted++#++(id: TransactionID, signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a new transaction is submitted by a `signer`.
+
+[.contract-item]
+[[IMultisig-TransactionConfirmed]]
+==== `[.contract-item-name]#++TransactionConfirmed++#++(id: TransactionID, signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a transaction is confirmed by a `signer`.
+
+[.contract-item]
+[[IMultisig-ConfirmationRevoked]]
+==== `[.contract-item-name]#++ConfirmationRevoked++#++(id: TransactionID, signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a `signer` revokes his confirmation.
+
+[.contract-item]
+[[IMultisig-TransactionExecuted]]
+==== `[.contract-item-name]#++TransactionExecuted++#++(id: TransactionID)++` [.item-kind]#event#
+
+Emitted when a transaction is executed.
+
+[.contract-item]
+[[IMultisig-CallSalt]]
+==== `[.contract-item-name]#++CallSalt++#++(id: felt252, salt: felt252)++` [.item-kind]#event#
+
+Emitted when a new transaction is submitted with non-zero salt.
+
+[.contract]
+[[MultisigComponent]]
+=== `++MultisigComponent++`
+
+:MultisigComponent-CallSalt: xref:MultisigComponent-CallSalt[CallSalt]
+:MultisigComponent-SignerAdded: xref:MultisigComponent-SignerAdded[SignerAdded]
+:MultisigComponent-SignerRemoved: xref:MultisigComponent-SignerRemoved[SignerRemoved]
+:MultisigComponent-QuorumUpdated: xref:MultisigComponent-QuorumUpdated[QuorumUpdated]
+:MultisigComponent-TransactionSubmitted: xref:MultisigComponent-TransactionSubmitted[TransactionSubmitted]
+:MultisigComponent-TransactionConfirmed: xref:MultisigComponent-TransactionConfirmed[TransactionConfirmed]
+:MultisigComponent-ConfirmationRevoked: xref:MultisigComponent-ConfirmationRevoked[ConfirmationRevoked]
+:MultisigComponent-TransactionExecuted: xref:MultisigComponent-TransactionExecuted[TransactionExecuted]
+
+[.hljs-theme-dark]
+```cairo
+use openzeppelin_governance::multisig::MultisigComponent;
+```
+
+Component that implements <> and provides functionality for multisignature wallets,
+including transaction management, quorum handling, and signer operations.
+
+[.contract-index]
+.Embeddable Implementations
+--
+.MultisigImpl
+
+* xref:#MultisigComponent-get_quorum[`++get_quorum(self)++`]
+* xref:#MultisigComponent-is_signer[`++is_signer(self, signer)++`]
+* xref:#MultisigComponent-get_signers[`++get_signers(self)++`]
+* xref:#MultisigComponent-is_confirmed[`++is_confirmed(self, id)++`]
+* xref:#MultisigComponent-is_confirmed_by[`++is_confirmed_by(self, id, signer)++`]
+* xref:#MultisigComponent-is_executed[`++is_executed(self, id)++`]
+* xref:#MultisigComponent-get_submitted_block[`++get_submitted_block(self, id)++`]
+* xref:#MultisigComponent-get_transaction_state[`++get_transaction_state(self, id)++`]
+* xref:#MultisigComponent-get_transaction_confirmations[`++get_transaction_confirmations(self, id)++`]
+* xref:#MultisigComponent-hash_transaction[`++hash_transaction(self, to, selector, calldata, salt)++`]
+* xref:#MultisigComponent-hash_transaction_batch[`++hash_transaction_batch(self, calls, salt)++`]
+* xref:#MultisigComponent-add_signers[`++add_signers(ref self, new_quorum, signers_to_add)++`]
+* xref:#MultisigComponent-remove_signers[`++remove_signers(ref self, new_quorum, signers_to_remove)++`]
+* xref:#MultisigComponent-replace_signer[`++replace_signer(ref self, signer_to_remove, signer_to_add)++`]
+* xref:#MultisigComponent-change_quorum[`++change_quorum(ref self, new_quorum)++`]
+* xref:#MultisigComponent-submit_transaction[`++submit_transaction(ref self, to, selector, calldata, salt)++`]
+* xref:#MultisigComponent-submit_transaction_batch[`++submit_transaction_batch(ref self, calls, salt)++`]
+* xref:#MultisigComponent-confirm_transaction[`++confirm_transaction(ref self, id)++`]
+* xref:#MultisigComponent-revoke_confirmation[`++revoke_confirmation(ref self, id)++`]
+* xref:#MultisigComponent-execute_transaction[`++execute_transaction(ref self, to, selector, calldata, salt)++`]
+* xref:#MultisigComponent-execute_transaction_batch[`++execute_transaction_batch(ref self, calls, salt)++`]
+--
+
+[.contract-index]
+.Internal Implementations
+--
+.InternalImpl
+
+* xref:#MultisigComponent-initializer[`++initializer(ref self, quorum, signers)++`]
+* xref:#MultisigComponent-resolve_tx_state[`++resolve_tx_state(self, id)++`]
+* xref:#MultisigComponent-assert_one_of_signers[`++assert_one_of_signers(self, caller)++`]
+* xref:#MultisigComponent-assert_tx_exists[`++assert_tx_exists(self, id)++`]
+* xref:#MultisigComponent-assert_only_self[`++assert_only_self(self)++`]
+* xref:#MultisigComponent-_add_signers[`++_add_signers(ref self, new_quorum, signers_to_add)++`]
+* xref:#MultisigComponent-_remove_signers[`++_remove_signers(ref self, new_quorum, signers_to_remove)++`]
+* xref:#MultisigComponent-_replace_signer[`++_replace_signer(ref self, signer_to_remove, signer_to_add)++`]
+* xref:#MultisigComponent-_change_quorum[`++_change_quorum(ref self, new_quorum)++`]
+--
+
+[.contract-index]
+.Events
+--
+* xref:#MultisigComponent-SignerAdded[`++SignerAdded(signer)++`]
+* xref:#MultisigComponent-SignerRemoved[`++SignerRemoved(signer)++`]
+* xref:#MultisigComponent-QuorumUpdated[`++QuorumUpdated(old_quorum, new_quorum)++`]
+* xref:#MultisigComponent-TransactionSubmitted[`++TransactionSubmitted(id, signer)++`]
+* xref:#MultisigComponent-TransactionConfirmed[`++TransactionConfirmed(id, signer)++`]
+* xref:#MultisigComponent-ConfirmationRevoked[`++ConfirmationRevoked(id, signer)++`]
+* xref:#MultisigComponent-TransactionExecuted[`++TransactionExecuted(id)++`]
+* xref:#MultisigComponent-CallSalt[`++CallSalt(id, salt)++`]
+--
+
+[#MultisigComponent-Functions]
+==== Embeddable functions
+
+[.contract-item]
+[[MultisigComponent-get_quorum]]
+==== `[.contract-item-name]#++get_quorum++#++(self: @ContractState) → u32++` [.item-kind]#external#
+
+Returns the current quorum value.
+
+[.contract-item]
+[[MultisigComponent-is_signer]]
+==== `[.contract-item-name]#++is_signer++#++(self: @ContractState, signer: ContractAddress) → bool++` [.item-kind]#external#
+
+Checks if a given `signer` is registered.
+
+[.contract-item]
+[[MultisigComponent-get_signers]]
+==== `[.contract-item-name]#++get_signers++#++(self: @ContractState) → Span++` [.item-kind]#external#
+
+Returns a list of all current signers.
+
+[.contract-item]
+[[MultisigComponent-is_confirmed]]
+==== `[.contract-item-name]#++is_confirmed++#++(self: @ContractState, id: TransactionID) → bool++` [.item-kind]#external#
+
+Returns whether the transaction with the given `id` has been confirmed. A confirmed transaction has received the required number of confirmations (quorum).
+
+[.contract-item]
+[[MultisigComponent-is_confirmed_by]]
+==== `[.contract-item-name]#++is_confirmed_by++#++(self: @ContractState, id: TransactionID, signer: ContractAddress) → bool++` [.item-kind]#external#
+
+Returns whether the transaction with the given `id` has been confirmed by the specified `signer`.
+
+[.contract-item]
+[[MultisigComponent-is_executed]]
+==== `[.contract-item-name]#++is_executed++#++(self: @ContractState, id: TransactionID) → bool++` [.item-kind]#external#
+
+Returns whether the transaction with the given `id` has been executed.
+
+[.contract-item]
+[[MultisigComponent-get_submitted_block]]
+==== `[.contract-item-name]#++get_submitted_block++#++(self: @ContractState, id: TransactionID) → u64++` [.item-kind]#external#
+
+Returns the block number when the transaction with the given `id` was submitted.
+
+[.contract-item]
+[[MultisigComponent-get_transaction_state]]
+==== `[.contract-item-name]#++get_transaction_state++#++(self: @ContractState, id: TransactionID) → TransactionState++` [.item-kind]#external#
+
+Returns the current state of the transaction with the given `id`.
+
+The possible states are:
+
+- `NotFound`: the transaction does not exist.
+- `Pending`: the transaction exists but hasn't reached the required confirmations.
+- `Confirmed`: the transaction has reached the required confirmations but hasn't been executed.
+- `Executed`: the transaction has been executed.
+
+[.contract-item]
+[[MultisigComponent-get_transaction_confirmations]]
+==== `[.contract-item-name]#++get_transaction_confirmations++#++(self: @ContractState, id: TransactionID) → u32++` [.item-kind]#external#
+
+Returns the number of confirmations from registered signers for the transaction with the specified `id`.
+
+[.contract-item]
+[[MultisigComponent-hash_transaction]]
+==== `[.contract-item-name]#++hash_transaction++#++(self: @ContractState, to: ContractAddress, selector: felt252, calldata: Span, salt: felt252)++` [.item-kind]#external#
+
+Returns the computed identifier of a transaction containing a single call.
+
+[.contract-item]
+[[MultisigComponent-hash_transaction_batch]]
+==== `[.contract-item-name]#++hash_transaction_batch++#++(self: @ContractState, calls: Span, salt: felt252)++` [.item-kind]#external#
+
+Returns the computed identifier of a transaction containing a batch of calls.
+
+[.contract-item]
+[[MultisigComponent-add_signers]]
+==== `[.contract-item-name]#++add_signers++#++(ref self: ContractState, new_quorum: u32, signers_to_add: Span)++` [.item-kind]#external#
+
+Adds new signers and updates the quorum.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `new_quorum` must be less than or equal to the total number of signers after addition.
+
+Emits a {MultisigComponent-SignerAdded} event for each signer added.
+
+Emits a {MultisigComponent-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[MultisigComponent-remove_signers]]
+==== `[.contract-item-name]#++remove_signers++#++(ref self: ContractState, new_quorum: u32, signers_to_remove: Span)++` [.item-kind]#external#
+
+Removes signers and updates the quorum.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `new_quorum` must be less than or equal to the total number of signers after removal.
+
+Emits a {MultisigComponent-SignerRemoved} event for each signer removed.
+
+Emits a {MultisigComponent-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[MultisigComponent-replace_signer]]
+==== `[.contract-item-name]#++replace_signer++#++(ref self: ContractState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress)++` [.item-kind]#external#
+
+Replaces an existing signer with a new signer.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `signer_to_remove` must be an existing signer.
+- `signer_to_add` must not be an existing signer.
+
+Emits a {MultisigComponent-SignerRemoved} event for the removed signer.
+
+Emits a {MultisigComponent-SignerAdded} event for the new signer.
+
+[.contract-item]
+[[MultisigComponent-change_quorum]]
+==== `[.contract-item-name]#++change_quorum++#++(ref self: ContractState, new_quorum: u32)++` [.item-kind]#external#
+
+Updates the quorum value to `new_quorum`.
+
+Requirements:
+
+- The caller must be the contract itself.
+- `new_quorum` must be non-zero.
+- `new_quorum` must be less than or equal to the total number of signers.
+
+Emits a {MultisigComponent-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[MultisigComponent-submit_transaction]]
+==== `[.contract-item-name]#++submit_transaction++#++(ref self: ContractState, to: ContractAddress, selector: felt252, calldata: Span, salt: felt252)++` [.item-kind]#external#
+
+Submits a new transaction for confirmation.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must not have been submitted before.
+
+Emits a {MultisigComponent-TransactionSubmitted} event.
+
+Emits a {MultisigComponent-CallSalt} event if `salt` is not zero.
+
+[.contract-item]
+[[MultisigComponent-submit_transaction_batch]]
+==== `[.contract-item-name]#++submit_transaction_batch++#++(ref self: ContractState, calls: Span, salt: felt252)++` [.item-kind]#external#
+
+Submits a new batch transaction for confirmation.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must not have been submitted before.
+
+Emits a {MultisigComponent-TransactionSubmitted} event.
+
+Emits a {MultisigComponent-CallSalt} event if `salt` is not zero.
+
+[.contract-item]
+[[MultisigComponent-confirm_transaction]]
+==== `[.contract-item-name]#++confirm_transaction++#++(ref self: ContractState, id: TransactionID)++` [.item-kind]#external#
+
+Confirms a transaction with the given `id`.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must exist and not be executed.
+- The caller must not have already confirmed the transaction.
+
+Emits a {MultisigComponent-TransactionConfirmed} event.
+
+[.contract-item]
+[[MultisigComponent-revoke_confirmation]]
+==== `[.contract-item-name]#++revoke_confirmation++#++(ref self: ContractState, id: TransactionID)++` [.item-kind]#external#
+
+Revokes a previous confirmation for a transaction with the given `id`.
+
+Requirements:
+
+- The transaction must exist and not be executed.
+- The caller must have previously confirmed the transaction.
+
+Emits a {MultisigComponent-ConfirmationRevoked} event.
+
+[.contract-item]
+[[MultisigComponent-execute_transaction]]
+==== `[.contract-item-name]#++execute_transaction++#++(ref self: ContractState, to: ContractAddress, selector: felt252, calldata: Span, salt: felt252)++` [.item-kind]#external#
+
+Executes a confirmed transaction.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must be confirmed and not yet executed.
+
+Emits a {MultisigComponent-TransactionExecuted} event.
+
+[.contract-item]
+[[MultisigComponent-execute_transaction_batch]]
+==== `[.contract-item-name]#++execute_transaction_batch++#++(ref self: ContractState, calls: Span, salt: felt252)++` [.item-kind]#external#
+
+Executes a confirmed batch transaction.
+
+Requirements:
+
+- The caller must be a registered signer.
+- The transaction must be confirmed and not yet executed.
+
+Emits a {MultisigComponent-TransactionExecuted} event.
+
+[#MultisigComponent-Internal-Functions]
+==== Internal functions
+
+[.contract-item]
+[[MultisigComponent-initializer]]
+==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, quorum: u32, signers: Span)++` [.item-kind]#internal#
+
+Initializes the Multisig component with the initial `quorum` and `signers`.
+This function must be called during contract initialization to set up the initial state.
+
+Requirements:
+
+- `quorum` must be non-zero and less than or equal to the number of `signers`.
+
+Emits a {MultisigComponent-SignerAdded} event for each signer added.
+
+Emits a {MultisigComponent-QuorumUpdated} event.
+
+[.contract-item]
+[[MultisigComponent-resolve_tx_state]]
+==== `[.contract-item-name]#++resolve_tx_state++#++(self: @ContractState, id: TransactionID) → TransactionState++` [.item-kind]#internal#
+
+Resolves and returns the current state of the transaction with the given `id`.
+
+The possible states are:
+
+- `NotFound`: the transaction does not exist.
+- `Pending`: the transaction exists but hasn't reached the required confirmations.
+- `Confirmed`: the transaction has reached the required confirmations but hasn't been executed.
+- `Executed`: the transaction has been executed.
+
+[.contract-item]
+[[MultisigComponent-assert_one_of_signers]]
+==== `[.contract-item-name]#++assert_one_of_signers++#++(self: @ContractState, caller: ContractAddress)++` [.item-kind]#internal#
+
+Asserts that the `caller` is one of the registered signers.
+
+Requirements:
+
+- The `caller` must be a registered signer.
+
+[.contract-item]
+[[MultisigComponent-assert_tx_exists]]
+==== `[.contract-item-name]#++assert_tx_exists++#++(self: @ContractState, id: TransactionID)++` [.item-kind]#internal#
+
+Asserts that a transaction with the given `id` exists.
+
+Requirements:
+
+- The transaction with the given `id` must have been submitted.
+
+[.contract-item]
+[[MultisigComponent-assert_only_self]]
+==== `[.contract-item-name]#++assert_only_self++#++(self: @ContractState)++` [.item-kind]#internal#
+
+Asserts that the caller is the contract itself.
+
+Requirements:
+
+- The caller must be the contract's own address.
+
+[.contract-item]
+[[MultisigComponent-_add_signers]]
+==== `[.contract-item-name]#++_add_signers++#++(ref self: ContractState, new_quorum: u32, signers_to_add: Span)++` [.item-kind]#internal#
+
+Adds new signers and updates the quorum.
+
+Requirements:
+
+- Each signer address must be non-zero.
+- `new_quorum` must be non-zero and less than or equal to the total number of signers after addition.
+
+Emits a {MultisigComponent-SignerAdded} event for each new signer added.
+
+Emits a {MultisigComponent-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[MultisigComponent-_remove_signers]]
+==== `[.contract-item-name]#++_remove_signers++#++(ref self: ContractState, new_quorum: u32, signers_to_remove: Span)++` [.item-kind]#internal#
+
+Removes existing signers and updates the quorum.
+
+Requirements:
+
+- `new_quorum` must be non-zero and less than or equal to the total number of signers
+after removal.
+
+Emits a {MultisigComponent-SignerRemoved} event for each signer removed.
+
+Emits a {MultisigComponent-QuorumUpdated} event if the quorum changes.
+
+[.contract-item]
+[[MultisigComponent-_replace_signer]]
+==== `[.contract-item-name]#++_replace_signer++#++(ref self: ContractState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress)++` [.item-kind]#internal#
+
+Replaces an existing signer with a new signer.
+
+Requirements:
+
+- `signer_to_remove` must be an existing signer.
+- `signer_to_add` must not be an existing signer.
+- `signer_to_add` must be a non-zero address.
+
+Emits a {MultisigComponent-SignerRemoved} event for the removed signer.
+
+Emits a {MultisigComponent-SignerAdded} event for the new signer.
+
+[.contract-item]
+[[MultisigComponent-_change_quorum]]
+==== `[.contract-item-name]#++_change_quorum++#++(ref self: ContractState, new_quorum: u32)++` [.item-kind]#internal#
+
+Updates the quorum value to `new_quorum` if it differs from the current quorum.
+
+Requirements:
+
+- `new_quorum` must be non-zero.
+- `new_quorum` must be less than or equal to the total number of signers.
+
+Emits a {MultisigComponent-QuorumUpdated} event if the quorum changes.
+
+[#MultisigComponent-Events]
+==== Events
+
+[.contract-item]
+[[MultisigComponent-SignerAdded]]
+==== `[.contract-item-name]#++SignerAdded++#++(signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a new `signer` is added.
+
+[.contract-item]
+[[MultisigComponent-SignerRemoved]]
+==== `[.contract-item-name]#++SignerRemoved++#++(signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a `signer` is removed.
+
+[.contract-item]
+[[MultisigComponent-QuorumUpdated]]
+==== `[.contract-item-name]#++QuorumUpdated++#++(old_quorum: u32, new_quorum: u32)++` [.item-kind]#event#
+
+Emitted when the `quorum` value is updated.
+
+[.contract-item]
+[[MultisigComponent-TransactionSubmitted]]
+==== `[.contract-item-name]#++TransactionSubmitted++#++(id: TransactionID, signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a new transaction is submitted by a `signer`.
+
+[.contract-item]
+[[MultisigComponent-TransactionConfirmed]]
+==== `[.contract-item-name]#++TransactionConfirmed++#++(id: TransactionID, signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a transaction is confirmed by a `signer`.
+
+[.contract-item]
+[[MultisigComponent-ConfirmationRevoked]]
+==== `[.contract-item-name]#++ConfirmationRevoked++#++(id: TransactionID, signer: ContractAddress)++` [.item-kind]#event#
+
+Emitted when a `signer` revokes his confirmation.
+
+[.contract-item]
+[[MultisigComponent-TransactionExecuted]]
+==== `[.contract-item-name]#++TransactionExecuted++#++(id: TransactionID)++` [.item-kind]#event#
+
+Emitted when a transaction is executed.
+
+[.contract-item]
+[[MultisigComponent-CallSalt]]
+==== `[.contract-item-name]#++CallSalt++#++(id: felt252, salt: felt252)++` [.item-kind]#event#
+
+Emitted when a new transaction is submitted with non-zero salt.
+
== Timelock
In a governance system, `TimelockControllerComponent` is in charge of introducing a delay between a proposal and its execution.
@@ -1990,6 +2782,12 @@ In a governance system, `TimelockControllerComponent` is in charge of introducin
[[ITimelock]]
=== `++ITimelock++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0/packages/governance/src/timelock/interface.cairo[{github-icon},role=heading-link]
+:ITimelock-CallScheduled: xref:ITimelock-CallScheduled[CallScheduled]
+:ITimelock-CallExecuted: xref:ITimelock-CallExecuted[CallExecuted]
+:ITimelock-CallSalt: xref:ITimelock-CallSalt[CallSalt]
+:ITimelock-CallCancelled: xref:ITimelock-CallCancelled[CallCancelled]
+:ITimelock-MinDelayChanged: xref:ITimelock-MinDelayChanged[MinDelayChanged]
+
[.hljs-theme-dark]
```cairo
use openzeppelin_governance::timelock::interface::ITimelock;
@@ -2069,7 +2867,14 @@ is `Done`.
[[ITimelock-get_operation_state]]
==== `[.contract-item-name]#++get_operation_state++#++(id: felt252) → OperationState++` [.item-kind]#external#
-Returns the OperationState for `id`.
+Returns the current state of the operation with the given `id`.
+
+The possible states are:
+
+- `Unset`: the operation has not been scheduled or has been canceled.
+- `Waiting`: the operation has been scheduled and is pending the scheduled delay.
+- `Ready`: the timer has expired, and the operation is eligible for execution.
+- `Done`: the operation has been executed.
[.contract-item]
[[ITimelock-get_min_delay]]
@@ -2100,8 +2905,8 @@ Requirements:
- The caller must have the `PROPOSER_ROLE` role.
-Emits {CallScheduled} event.
-Emits {CallSalt} event if `salt` is not zero.
+Emits {ITimelock-CallScheduled} event.
+Emits {ITimelock-CallSalt} event if `salt` is not zero.
[.contract-item]
[[ITimelock-schedule_batch]]
@@ -2113,21 +2918,21 @@ Requirements:
- The caller must have the `PROPOSER_ROLE` role.
-Emits one {CallScheduled} event for each transaction in the batch.
-Emits {CallSalt} event if `salt` is not zero.
+Emits one {ITimelock-CallScheduled} event for each transaction in the batch.
+Emits {ITimelock-CallSalt} event if `salt` is not zero.
[.contract-item]
[[ITimelock-cancel]]
==== `[.contract-item-name]#++cancel++#++(id: felt252)++` [.item-kind]#external#
-Cancel an operation.
+Cancels an operation. A canceled operation returns to `Unset` OperationState.
Requirements:
- The caller must have the `CANCELLER_ROLE` role.
-- `id` must be an operation.
+- `id` must be a pending operation.
-Emits a {CallCancelled} event.
+Emits a {ITimelock-CallCancelled} event.
[.contract-item]
[[ITimelock-execute]]
@@ -2141,7 +2946,7 @@ Requirements:
- `id` must be in Ready OperationState.
- `predecessor` must either be `0` or in Done OperationState.
-Emits a {CallExecuted} event.
+Emits a {ITimelock-CallExecuted} event.
NOTE: This function can reenter, but it doesn't pose a risk because <>
checks that the proposal is pending, thus any modifications to the operation during
@@ -2159,7 +2964,7 @@ Requirements:
- `id` must be in Ready OperationState.
- `predecessor` must either be `0` or in Done OperationState.
-Emits a {CallExecuted} event for each Call.
+Emits a {ITimelock-CallExecuted} event for each Call.
NOTE: This function can reenter, but it doesn't pose a risk because `_after_call`
checks that the proposal is pending, thus any modifications to the operation during
@@ -2177,7 +2982,7 @@ Requirements:
and later executing an operation where the timelock is the target and the data
is the serialized call to this function.
-Emits a {MinDelayChanged} event.
+Emits a {ITimelock-MinDelayChanged} event.
[#ITimelock-Events]
==== Events
@@ -2216,6 +3021,12 @@ Emitted when the minimum delay for future operations is modified.
[[TimelockControllerComponent]]
=== `++TimelockControllerComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0/packages/governance/src/timelock/timelock_controller.cairo[{github-icon},role=heading-link]
+:TimelockComponent-CallScheduled: xref:TimelockControllerComponent-CallScheduled[CallScheduled]
+:TimelockComponent-CallExecuted: xref:TimelockControllerComponent-CallExecuted[CallExecuted]
+:TimelockComponent-CallSalt: xref:TimelockControllerComponent-CallSalt[CallSalt]
+:TimelockComponent-CallCancelled: xref:TimelockControllerComponent-CallCancelled[CallCancelled]
+:TimelockComponent-MinDelayChanged: xref:TimelockControllerComponent-MinDelayChanged[MinDelayChanged]
+
include::../utils/_common.adoc[]
[.hljs-theme-dark]
@@ -2344,7 +3155,14 @@ is `Done`.
[[TimelockControllerComponent-get_operation_state]]
==== `[.contract-item-name]#++get_operation_state++#++(self: @ContractState, id: felt252) → OperationState++` [.item-kind]#external#
-Returns the OperationState for `id`.
+Returns the current state of the operation with the given `id`.
+
+The possible states are:
+
+- `Unset`: the operation has not been scheduled or has been canceled.
+- `Waiting`: the operation has been scheduled and is pending the scheduled delay.
+- `Ready`: the timer has expired, and the operation is eligible for execution.
+- `Done`: the operation has been executed.
[.contract-item]
[[TimelockControllerComponent-get_min_delay]]
@@ -2377,8 +3195,8 @@ Requirements:
- The proposal must not already exist.
- `delay` must be greater than or equal to the min delay.
-Emits {CallScheduled} event.
-Emits {CallSalt} event if `salt` is not zero.
+Emits {TimelockComponent-CallScheduled} event.
+Emits {TimelockComponent-CallSalt} event if `salt` is not zero.
[.contract-item]
[[TimelockControllerComponent-schedule_batch]]
@@ -2392,21 +3210,21 @@ Requirements:
- The proposal must not already exist.
- `delay` must be greater than or equal to the min delay.
-Emits one {CallScheduled} event for each transaction in the batch.
-Emits {CallSalt} event if `salt` is not zero.
+Emits one {TimelockComponent-CallScheduled} event for each transaction in the batch.
+Emits {TimelockComponent-CallSalt} event if `salt` is not zero.
[.contract-item]
[[TimelockControllerComponent-cancel]]
==== `[.contract-item-name]#++cancel++#++(ref self: ContractState, id: felt252)++` [.item-kind]#external#
-Cancel an operation.
+Cancels an operation. A canceled operation returns to `Unset` OperationState.
Requirements:
- The caller must have the `CANCELLER_ROLE` role.
-- `id` must be an operation.
+- `id` must be a pending operation.
-Emits a {CallCancelled} event.
+Emits a {TimelockComponent-CallCancelled} event.
[.contract-item]
[[TimelockControllerComponent-execute]]
@@ -2420,7 +3238,7 @@ Requirements:
- `id` must be in Ready OperationState.
- `predecessor` must either be `0` or in Done OperationState.
-Emits a {CallExecuted} event.
+Emits a {TimelockComponent-CallExecuted} event.
NOTE: This function can reenter, but it doesn't pose a risk because <>
checks that the proposal is pending, thus any modifications to the operation during
@@ -2438,7 +3256,7 @@ Requirements:
- `id` must be in Ready OperationState.
- `predecessor` must either be `0` or in Done OperationState.
-Emits a {CallExecuted} event for each Call.
+Emits a {TimelockComponent-CallExecuted} event for each Call.
NOTE: This function can reenter, but it doesn't pose a risk because `_after_call`
checks that the proposal is pending, thus any modifications to the operation during
@@ -2456,7 +3274,7 @@ Requirements:
and later executing an operation where the timelock is the target and the data
is the serialized call to this function.
-Emits a {MinDelayChanged} event.
+Emits a {TimelockComponent-MinDelayChanged} event.
[#TimelockControllerComponent-Internal-Functions]
==== Internal functions
@@ -2478,15 +3296,15 @@ WARNING: The optional admin can aid with initial configuration of roles after de
without being subject to delay, but this role should be subsequently renounced in favor of
administration through timelocked proposals.
-Emits two {RoleGranted} events for each account in `proposers` with `PROPOSER_ROLE` and
+Emits two {IAccessControl-RoleGranted} events for each account in `proposers` with `PROPOSER_ROLE` and
`CANCELLER_ROLE` roles.
-Emits a {RoleGranted} event for each account in `executors` with `EXECUTOR_ROLE` role.
+Emits a {IAccessControl-RoleGranted} event for each account in `executors` with `EXECUTOR_ROLE` role.
-May emit a {RoleGranted} event for `admin` with `DEFAULT_ADMIN_ROLE` role (if `admin` is
+May emit a {IAccessControl-RoleGranted} event for `admin` with `DEFAULT_ADMIN_ROLE` role (if `admin` is
not zero).
-Emits {MinDelayChanged} event.
+Emits {TimelockComponent-MinDelayChanged} event.
[.contract-item]
[[TimelockControllerComponent-assert_only_role]]
@@ -2785,9 +3603,9 @@ Returns the delegate that `account` has chosen.
Delegates votes from the sender to `delegatee`.
-Emits a {DelegateChanged} event.
+Emits a {VotesComponent-DelegateChanged} event.
-May emit one or two {DelegateVotesChanged} events.
+May emit one or two {VotesComponent-DelegateVotesChanged} events.
[.contract-item]
[[VotesComponent-delegate_by_sig]]
@@ -2802,9 +3620,9 @@ Requirements:
- `delegator` must implement `SRC6::is_valid_signature`.
- `signature` should be valid for the message hash.
-Emits a {DelegateChanged} event.
+Emits a {VotesComponent-DelegateChanged} event.
-May emit one or two {DelegateVotesChanged} events.
+May emit one or two {VotesComponent-DelegateVotesChanged} events.
[#VotesComponent-Internal-functions]
==== Internal functions
@@ -2821,7 +3639,7 @@ Returns the current total supply of votes.
Moves delegated votes from one delegate to another.
-May emit one or two {DelegateVotesChanged} events.
+May emit one or two {VotesComponent-DelegateVotesChanged} events.
[.contract-item]
[[VotesComponent-transfer_voting_units]]
@@ -2834,7 +3652,7 @@ should be zero. Total supply of voting units will be adjusted with mints and bur
WARNING: If voting units are based on an underlying transferable asset (like a token), you must call this function every time the asset is transferred to keep the internal voting power accounting in sync. For ERC20 and ERC721 tokens, this is typically handled using hooks.
-May emit one or two {DelegateVotesChanged} events.
+May emit one or two {VotesComponent-DelegateVotesChanged} events.
[.contract-item]
[[VotesComponent-num_checkpoints]]
@@ -2854,9 +3672,9 @@ Returns the `pos`-th checkpoint for `account`.
Delegates all of ``account``'s voting units to `delegatee`.
-Emits a {DelegateChanged} event.
+Emits a {VotesComponent-DelegateChanged} event.
-May emit one or two {DelegateVotesChanged} events.
+May emit one or two {VotesComponent-DelegateVotesChanged} events.
[#VotesComponent-Events]
==== Events
diff --git a/docs/modules/ROOT/pages/governance/governor.adoc b/docs/modules/ROOT/pages/governance/governor.adoc
index 62b0e20ac..e800344ea 100644
--- a/docs/modules/ROOT/pages/governance/governor.adoc
+++ b/docs/modules/ROOT/pages/governance/governor.adoc
@@ -1,4 +1,5 @@
= 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]
diff --git a/docs/modules/ROOT/pages/governance/multisig.adoc b/docs/modules/ROOT/pages/governance/multisig.adoc
new file mode 100644
index 000000000..d1979c635
--- /dev/null
+++ b/docs/modules/ROOT/pages/governance/multisig.adoc
@@ -0,0 +1,154 @@
+= Multisig
+
+:multisig-component: xref:api/governance.adoc#MultisigComponent[MultisigComponent]
+:snip12-metadata: xref:api/utilities.adoc#snip12[SNIP12Metadata]
+
+The Multisig component implements a multi-signature mechanism to enhance the security and
+governance of smart contract transactions. It ensures that no single signer can unilaterally
+execute critical actions, requiring multiple registered signers to approve and collectively
+execute transactions.
+
+This component is designed to secure operations such as fund management or protocol governance,
+where collective decision-making is essential. The Multisig Component is self-administered,
+meaning that changes to signers or quorum must be approved through the multisig process itself.
+
+== Key features
+
+- *Multi-Signature Security*: transactions must be approved by multiple signers, ensuring
+distributed governance.
+
+- *Quorum Enforcement*: defines the minimum number of approvals required for transaction execution.
+
+- *Self-Administration*: all modifications to the component (e.g., adding or removing signers)
+must pass through the multisig process.
+
+- *Event Logging*: provides comprehensive event logging for transparency and auditability.
+
+== Signer management
+
+The Multisig component introduces the concept of signers and quorum:
+
+- *Signers*: only registered signers can submit, confirm, revoke, or execute transactions. The Multisig
+Component supports adding, removing, or replacing signers.
+- *Quorum*: the quorum defines the minimum number of confirmations required to approve a transaction.
+
+NOTE: To prevent unauthorized modifications, only the contract itself can add, remove, or replace signers or change the quorum.
+This ensures that all modifications pass through the multisig approval process.
+
+== Transaction lifecycle
+
+The state of a transaction is represented by the `TransactionState` enum and can be retrieved
+by calling the `get_transaction_state` function with the transaction's identifier.
+
+The identifier of a multisig transaction is a `felt252` value, computed as the Pedersen hash
+of the transaction's calls and salt. It can be computed by invoking the implementing contract's
+`hash_transaction` method for single-call transactions or `hash_transaction_batch` for multi-call
+transactions. Submitting a transaction with identical calls and the same salt value a second time
+will fail, as transaction identifiers must be unique. To resolve this, use a different salt value
+to generate a unique identifier.
+
+A transaction in the Multisig component follows a specific lifecycle:
+
+`NotFound` → `Pending` → `Confirmed` → `Executed`
+
+- *NotFound*: the transaction does not exist.
+- *Pending*: the transaction exists but has not reached the required confirmations.
+- *Confirmed*: the transaction has reached the quorum but has not yet been executed.
+- *Executed*: the transaction has been successfully executed.
+
+== Usage
+
+Integrating the Multisig functionality into a contract requires implementing {multisig-component}.
+The contract's constructor should initialize the component with a quorum value and a list of initial signers.
+
+Here's an example of a simple wallet contract featuring the Multisig functionality:
+
+[,cairo]
+----
+#[starknet::contract]
+mod MultisigWallet {
+ use openzeppelin_governance::multisig::MultisigComponent;
+ use starknet::ContractAddress;
+
+ component!(path: MultisigComponent, storage: multisig, event: MultisigEvent);
+
+ #[abi(embed_v0)]
+ impl MultisigImpl = MultisigComponent::MultisigImpl;
+ impl MultisigInternalImpl = MultisigComponent::InternalImpl;
+
+ #[storage]
+ struct Storage {
+ #[substorage(v0)]
+ multisig: MultisigComponent::Storage,
+ }
+
+ #[event]
+ #[derive(Drop, starknet::Event)]
+ enum Event {
+ #[flat]
+ MultisigEvent: MultisigComponent::Event,
+ }
+
+ #[constructor]
+ fn constructor(ref self: ContractState, quorum: u32, signers: Span) {
+ self.multisig.initializer(quorum, signers);
+ }
+}
+----
+
+== Interface
+
+This is the interface of a contract implementing the {multisig-component}:
+
+[,cairo]
+----
+#[starknet::interface]
+pub trait MultisigABI {
+ // Read functions
+ fn get_quorum(self: @TState) -> u32;
+ fn is_signer(self: @TState, signer: ContractAddress) -> bool;
+ fn get_signers(self: @TState) -> Span;
+ fn is_confirmed(self: @TState, id: TransactionID) -> bool;
+ fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool;
+ fn is_executed(self: @TState, id: TransactionID) -> bool;
+ fn get_submitted_block(self: @TState, id: TransactionID) -> u64;
+ fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState;
+ fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32;
+ fn hash_transaction(
+ self: @TState,
+ to: ContractAddress,
+ selector: felt252,
+ calldata: Span,
+ salt: felt252,
+ ) -> TransactionID;
+ fn hash_transaction_batch(self: @TState, calls: Span, salt: felt252) -> TransactionID;
+
+ // Write functions
+ fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span);
+ fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span);
+ fn replace_signer(
+ ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress,
+ );
+ fn change_quorum(ref self: TState, new_quorum: u32);
+ fn submit_transaction(
+ ref self: TState,
+ to: ContractAddress,
+ selector: felt252,
+ calldata: Span,
+ salt: felt252,
+ ) -> TransactionID;
+ fn submit_transaction_batch(
+ ref self: TState, calls: Span, salt: felt252,
+ ) -> TransactionID;
+ fn confirm_transaction(ref self: TState, id: TransactionID);
+ fn revoke_confirmation(ref self: TState, id: TransactionID);
+ fn execute_transaction(
+ ref self: TState,
+ to: ContractAddress,
+ selector: felt252,
+ calldata: Span,
+ salt: felt252,
+ );
+ fn execute_transaction_batch(ref self: TState, calls: Span, salt: felt252);
+}
+----
diff --git a/docs/modules/ROOT/pages/governance/timelock.adoc b/docs/modules/ROOT/pages/governance/timelock.adoc
index 7fb13fef6..6f016e5ba 100644
--- a/docs/modules/ROOT/pages/governance/timelock.adoc
+++ b/docs/modules/ROOT/pages/governance/timelock.adoc
@@ -4,17 +4,31 @@
: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:
+The state of an operation is represented by the `OperationState` enum and can be retrieved
+by calling the `get_operation_state` function with the operation's identifier.
+
+The identifier of an operation is a `felt252` value, computed as the Pedersen hash of the
+operation's call or calls, its predecessor, and salt. It can be computed by invoking the
+implementing contract's `hash_operation` function for single-call operations or
+`hash_operation_batch` for multi-call operations. Submitting an operation with identical calls,
+predecessor, and the same salt value a second time will fail, as operation identifiers must be
+unique. To resolve this, use a different salt value to generate a unique identifier.
+
+Timelocked operations follow a specific lifecycle:
`Unset` → `Waiting` → `Ready` → `Done`
+- `Unset`: the operation has not been scheduled or has been canceled.
+- `Waiting`: the operation has been scheduled and is pending the scheduled delay.
+- `Ready`: the timer has expired, and the operation is eligible for execution.
+- `Done`: the operation has been executed.
+
== Timelock flow
=== Schedule
diff --git a/docs/modules/ROOT/pages/governance/votes.adoc b/docs/modules/ROOT/pages/governance/votes.adoc
index d66ea7041..67df15525 100644
--- a/docs/modules/ROOT/pages/governance/votes.adoc
+++ b/docs/modules/ROOT/pages/governance/votes.adoc
@@ -4,18 +4,17 @@
: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]
+:votes-usage: xref:Usage[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
+== 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. +
@@ -29,7 +28,6 @@ Additionally, you must implement the {nonces-component} and the {snip12-metadata
Here's an example of how to structure a simple ERC20Votes contract:
-
[source,cairo]
----
#[starknet::contract]
diff --git a/packages/governance/src/multisig/multisig.cairo b/packages/governance/src/multisig/multisig.cairo
index b641f2272..27f103bda 100644
--- a/packages/governance/src/multisig/multisig.cairo
+++ b/packages/governance/src/multisig/multisig.cairo
@@ -190,14 +190,20 @@ pub mod MultisigComponent {
self.Multisig_tx_info.read(id).is_executed
}
+ /// Returns the block number when the transaction with the given `id` was submitted.
+ fn get_submitted_block(self: @ComponentState, id: TransactionID) -> u64 {
+ self.Multisig_tx_info.read(id).submitted_block
+ }
+
/// Returns the current state of the transaction with the given `id`.
///
/// The possible states are:
- /// - `NotFound`: The transaction does not exist.
- /// - `Pending`: The transaction exists but hasn't reached the required confirmations.
- /// - `Confirmed`: The transaction has reached the required confirmations but hasn't been
+ ///
+ /// - `NotFound`: the transaction does not exist.
+ /// - `Pending`: the transaction exists but hasn't reached the required confirmations.
+ /// - `Confirmed`: the transaction has reached the required confirmations but hasn't been
/// executed.
- /// - `Executed`: The transaction has been executed.
+ /// - `Executed`: the transaction has been executed.
fn get_transaction_state(
self: @ComponentState, id: TransactionID,
) -> TransactionState {
@@ -219,9 +225,23 @@ pub mod MultisigComponent {
result
}
- /// Returns the block number when the transaction with the given `id` was submitted.
- fn get_submitted_block(self: @ComponentState, id: TransactionID) -> u64 {
- self.Multisig_tx_info.read(id).submitted_block
+ /// Returns the computed identifier of a transaction containing a single call.
+ fn hash_transaction(
+ self: @ComponentState,
+ to: ContractAddress,
+ selector: felt252,
+ calldata: Span,
+ salt: felt252,
+ ) -> TransactionID {
+ let call = Call { to, selector, calldata };
+ self.hash_transaction_batch(array![call].span(), salt)
+ }
+
+ /// Returns the computed identifier of a transaction containing a batch of calls.
+ fn hash_transaction_batch(
+ self: @ComponentState, calls: Span, salt: felt252,
+ ) -> TransactionID {
+ PedersenTrait::new(0).update_with(calls).update_with(salt).finalize()
}
/// Adds new signers and updates the quorum.
@@ -232,6 +252,7 @@ pub mod MultisigComponent {
/// - `new_quorum` must be less than or equal to the total number of signers after addition.
///
/// Emits a `SignerAdded` event for each signer added.
+ ///
/// Emits a `QuorumUpdated` event if the quorum changes.
fn add_signers(
ref self: ComponentState,
@@ -250,6 +271,7 @@ pub mod MultisigComponent {
/// - `new_quorum` must be less than or equal to the total number of signers after removal.
///
/// Emits a `SignerRemoved` event for each signer removed.
+ ///
/// Emits a `QuorumUpdated` event if the quorum changes.
fn remove_signers(
ref self: ComponentState,
@@ -270,6 +292,7 @@ pub mod MultisigComponent {
/// - `signer_to_add` must be a non-zero address.
///
/// Emits a `SignerRemoved` event for the removed signer.
+ ///
/// Emits a `SignerAdded` event for the new signer.
fn replace_signer(
ref self: ComponentState,
@@ -302,6 +325,7 @@ pub mod MultisigComponent {
/// - The transaction must not have been submitted before.
///
/// Emits a `TransactionSubmitted` event.
+ ///
/// Emits a `CallSalt` event if `salt` is not zero.
fn submit_transaction(
ref self: ComponentState,
@@ -322,6 +346,7 @@ pub mod MultisigComponent {
/// - The transaction must not have been submitted before.
///
/// Emits a `TransactionSubmitted` event.
+ ///
/// Emits a `CallSalt` event if `salt` is not zero.
fn submit_transaction_batch(
ref self: ComponentState, calls: Span, salt: felt252,
@@ -430,25 +455,6 @@ pub mod MultisigComponent {
},
};
}
-
- /// Returns the computed identifier of a transaction containing a single call.
- fn hash_transaction(
- self: @ComponentState,
- to: ContractAddress,
- selector: felt252,
- calldata: Span,
- salt: felt252,
- ) -> TransactionID {
- let call = Call { to, selector, calldata };
- self.hash_transaction_batch(array![call].span(), salt)
- }
-
- /// Returns the computed identifier of a transaction containing a batch of calls.
- fn hash_transaction_batch(
- self: @ComponentState, calls: Span, salt: felt252,
- ) -> TransactionID {
- PedersenTrait::new(0).update_with(calls).update_with(salt).finalize()
- }
}
//
@@ -467,7 +473,8 @@ pub mod MultisigComponent {
/// - `quorum` must be non-zero and less than or equal to the number of `signers`.
///
/// Emits a `SignerAdded` event for each signer added.
- /// Emits a `QuorumUpdated` event if the quorum changes.
+ ///
+ /// Emits a `QuorumUpdated` event.
fn initializer(
ref self: ComponentState, quorum: u32, signers: Span,
) {
@@ -477,11 +484,12 @@ pub mod MultisigComponent {
/// Resolves and returns the current state of the transaction with the given `id`.
///
/// The possible states are:
- /// - `NotFound`: The transaction does not exist.
- /// - `Pending`: The transaction exists but hasn't reached the required confirmations.
- /// - `Confirmed`: The transaction has reached the required confirmations but hasn't been
+ ///
+ /// - `NotFound`: the transaction does not exist.
+ /// - `Pending`: the transaction exists but hasn't reached the required confirmations.
+ /// - `Confirmed`: the transaction has reached the required confirmations but hasn't been
/// executed.
- /// - `Executed`: The transaction has been executed.
+ /// - `Executed`: the transaction has been executed.
fn resolve_tx_state(
self: @ComponentState, id: TransactionID,
) -> TransactionState {
@@ -513,7 +521,7 @@ pub mod MultisigComponent {
///
/// Requirements:
///
- /// - The transaction with `id` must have been submitted.
+ /// - The transaction with the given `id` must have been submitted.
fn assert_tx_exists(self: @ComponentState, id: TransactionID) {
assert(self.get_submitted_block(id).is_non_zero(), Errors::TX_NOT_FOUND);
}
@@ -538,6 +546,7 @@ pub mod MultisigComponent {
/// after addition.
///
/// Emits a `SignerAdded` event for each new signer added.
+ ///
/// Emits a `QuorumUpdated` event if the quorum changes.
fn _add_signers(
ref self: ComponentState,
@@ -573,6 +582,7 @@ pub mod MultisigComponent {
/// after removal.
///
/// Emits a `SignerRemoved` event for each signer removed.
+ ///
/// Emits a `QuorumUpdated` event if the quorum changes.
fn _remove_signers(
ref self: ComponentState,
@@ -616,6 +626,7 @@ pub mod MultisigComponent {
/// - `signer_to_add` must be a non-zero address.
///
/// Emits a `SignerRemoved` event for the removed signer.
+ ///
/// Emits a `SignerAdded` event for the new signer.
fn _replace_signer(
ref self: ComponentState,
diff --git a/packages/governance/src/timelock/timelock_controller.cairo b/packages/governance/src/timelock/timelock_controller.cairo
index a241e1852..9e70887e7 100644
--- a/packages/governance/src/timelock/timelock_controller.cairo
+++ b/packages/governance/src/timelock/timelock_controller.cairo
@@ -153,6 +153,13 @@ pub mod TimelockControllerComponent {
}
/// Returns the OperationState for `id`.
+ ///
+ /// The possible states are:
+ ///
+ /// - `Unset`: the operation has not been scheduled or has been canceled.
+ /// - `Waiting`: the operation has been scheduled and is pending the scheduled delay.
+ /// - `Ready`: the timer has expired, and the operation is eligible for execution.
+ /// - `Done`: the operation has been executed.
fn get_operation_state(
self: @ComponentState, id: felt252,
) -> OperationState {
@@ -260,7 +267,7 @@ pub mod TimelockControllerComponent {
}
}
- /// Cancels an operation.
+ /// Cancels an operation. A canceled operation returns to `Unset` OperationState.
///
/// Requirements:
///