Skip to content

Commit

Permalink
Multisig Docs (#1254)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
immrsd authored Dec 18, 2024
1 parent 14df055 commit e28b987
Show file tree
Hide file tree
Showing 8 changed files with 1,085 additions and 81 deletions.
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
902 changes: 860 additions & 42 deletions docs/modules/ROOT/pages/api/governance.adoc

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/governance/governor.adoc
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
154 changes: 154 additions & 0 deletions docs/modules/ROOT/pages/governance/multisig.adoc
Original file line number Diff line number Diff line change
@@ -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<ContractState>;
impl MultisigInternalImpl = MultisigComponent::InternalImpl<ContractState>;
#[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<ContractAddress>) {
self.multisig.initializer(quorum, signers);
}
}
----

== Interface

This is the interface of a contract implementing the {multisig-component}:

[,cairo]
----
#[starknet::interface]
pub trait MultisigABI<TState> {
// Read functions
fn get_quorum(self: @TState) -> u32;
fn is_signer(self: @TState, signer: ContractAddress) -> bool;
fn get_signers(self: @TState) -> Span<ContractAddress>;
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<felt252>,
salt: felt252,
) -> TransactionID;
fn hash_transaction_batch(self: @TState, calls: Span<Call>, salt: felt252) -> TransactionID;
// Write functions
fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span<ContractAddress>);
fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span<ContractAddress>);
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<felt252>,
salt: felt252,
) -> TransactionID;
fn submit_transaction_batch(
ref self: TState, calls: Span<Call>, 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<felt252>,
salt: felt252,
);
fn execute_transaction_batch(ref self: TState, calls: Span<Call>, salt: felt252);
}
----
18 changes: 16 additions & 2 deletions docs/modules/ROOT/pages/governance/timelock.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions docs/modules/ROOT/pages/governance/votes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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. +
Expand All @@ -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]
Expand Down
Loading

0 comments on commit e28b987

Please sign in to comment.