Skip to content

Commit

Permalink
Add Governor component (OpenZeppelin#1180)
Browse files Browse the repository at this point in the history
* feat: add features

* feat: remove access dual dispatchers (OpenZeppelin#1154)

* feat: bump scarb

* feat: update CHANGELOG

* feat: add features

* feat: remove account dual dispatchers (OpenZeppelin#1168)

* feat: move mocks to test_common

* Remove token dual dispatchers (OpenZeppelin#1175)

* feat: remove modules

* fix: mock

* fix: linter

* fix: tests

* fix: mock

* feat: apply review suggestions

* feat: update docs

* feat: update CHANGELOG

* fix: typo

* fix: mod

* feat: remove unused imports

* fix: README

* feat: move mocks into test_common

* feat: remove mocks from release target

* fix: mock

* fix: imports

* feat: bumo scarb and remove assert_macros from manifest

* feat: re-add assert_macros

* feat: add test target names

* docs: update index

* feat: add interface

* feat: add double ended queue struct

* feat: add proposal core

* +

* feat: add extension traits

* feat: add hash_proposal

* feat: add state internal function

* feat: add bytearray to utils

* fix: bytearray trait types

* feat: apply stash

* feat: add propose mechanism

* feat: add queue mechanism

* feat: add execute and cancel

* feat: add main trait

* feat: finish first extension

* feat: add votes quorum fractional extension main logic

* feat: add settings extension

* feat: add governor votes extension

* feat: add core execution extension

* feat: add timelock execution extension

* refactor: dependencies

* feat: add mock

* fix: linter

* fix: comment

* feat: add some tests

* fix: linter

* refactor: comments

* fix: import path

* fix: test

* feat: add relay mechanism

* fix: conditions

* feat: add more tests

* fix: linter

* feat: add more tests

* feat: add more tests

* Update packages/governance/src/governor/interface.cairo

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>

* Update packages/governance/src/governor/interface.cairo

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>

* Update packages/governance/src/governor/governor.cairo

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>

* Update packages/governance/src/governor/governor.cairo

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>

* Update packages/governance/src/governor/governor.cairo

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>

* Update packages/governance/src/governor/proposal_core.cairo

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>

* feat: apply review updates

* fix: linter

* fix: bytearray

* feat: add more tests

* feat: add more tests

* Update packages/utils/src/bytearray.cairo

Co-authored-by: immrsd <103599616+immrsd@users.noreply.github.com>

* feat: apply review updates

* feat: add cast vote by sig

* feat: add more tests

* feat: add more tests

* fix: remove import

* feat: add yet more tests

* feat: update CHANGELOG

* feat: apply review updates

* feat: add tests for governor core execution

* feat: add more tests

* feat: add more tests

* feat: add more tests

* fix: linter

* feat: add more tests

* feat: add more tests

* feat: add more tests

* fix: warnings

* feat: add more tests

* feat: add more tests

* fix: linter

---------

Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>
Co-authored-by: immrsd <103599616+immrsd@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 20, 2024
1 parent 45b9a72 commit f0edfaa
Show file tree
Hide file tree
Showing 37 changed files with 7,761 additions and 83 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- SRC9 (Outside Execution) integration to account presets (#1201)
- `SNIP12HashSpanImpl` to `openzeppelin_utils::cryptography::snip12` (#1180)
- GovernorComponent with the following extensions: (#1180)
- GovernorCoreExecutionComponent
- GovernorCountingSimpleComponent
- GovernorSettingsComponent
- GovernorTimelockExecutionComponent
- GovernorVotesQuorumFractionComponent
- GovernorVotesComponent
- `is_tx_version_valid` utility function to `openzeppelin_account::utils` (#1224)

### Changed
Expand Down
2 changes: 2 additions & 0 deletions packages/governance/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ casm = false
name = "openzeppelin_governance_unittest"
build-external-contracts = [
"openzeppelin_test_common::mocks::account::SnakeAccountMock",
"openzeppelin_test_common::mocks::governor::GovernorMock",
"openzeppelin_test_common::mocks::governor::GovernorTimelockedMock",
"openzeppelin_test_common::mocks::timelock::TimelockControllerMock",
"openzeppelin_test_common::mocks::timelock::MockContract",
"openzeppelin_test_common::mocks::timelock::TimelockAttackerMock",
Expand Down
8 changes: 8 additions & 0 deletions packages/governance/src/governor.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod extensions;
pub mod governor;
pub mod interface;
pub mod proposal_core;
pub mod vote;

pub use governor::{GovernorComponent, DefaultConfig};
pub use proposal_core::ProposalCore;
14 changes: 14 additions & 0 deletions packages/governance/src/governor/extensions.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub mod governor_core_execution;
pub mod governor_counting_simple;
pub mod governor_settings;
pub mod governor_timelock_execution;
pub mod governor_votes;
pub mod governor_votes_quorum_fraction;
pub mod interface;

pub use governor_core_execution::GovernorCoreExecutionComponent;
pub use governor_counting_simple::GovernorCountingSimpleComponent;
pub use governor_settings::GovernorSettingsComponent;
pub use governor_timelock_execution::GovernorTimelockExecutionComponent;
pub use governor_votes::GovernorVotesComponent;
pub use governor_votes_quorum_fraction::GovernorVotesQuorumFractionComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.19.0
// (governance/governor/extensions/governor_core_execution.cairo)

/// # GovernorCoreExecution Component
///
/// Extension of GovernorComponent providing an execution mechanism directly through
/// the Governor itself. For a timelocked execution mechanism, see
/// GovernorTimelockExecutionComponent.
#[starknet::component]
pub mod GovernorCoreExecutionComponent {
use crate::governor::GovernorComponent::{
InternalExtendedTrait, ComponentState as GovernorComponentState
};
use crate::governor::GovernorComponent;
use crate::governor::interface::ProposalState;
use openzeppelin_introspection::src5::SRC5Component;
use starknet::account::Call;
use starknet::{ContractAddress, SyscallResultTrait};

#[storage]
pub struct Storage {}

//
// Extensions
//

pub impl GovernorExecution<
TContractState,
+GovernorComponent::HasComponent<TContractState>,
+GovernorComponent::GovernorCountingTrait<TContractState>,
+GovernorComponent::GovernorSettingsTrait<TContractState>,
+GovernorComponent::GovernorVotesTrait<TContractState>,
+SRC5Component::HasComponent<TContractState>,
impl GovernorCoreExecution: HasComponent<TContractState>,
+Drop<TContractState>
> of GovernorComponent::GovernorExecutionTrait<TContractState> {
/// See `GovernorComponent::GovernorExecutionTrait::state`.
fn state(
self: @GovernorComponentState<TContractState>, proposal_id: felt252
) -> ProposalState {
self._state(proposal_id)
}

/// See `GovernorComponent::GovernorExecutionTrait::executor`.
fn executor(self: @GovernorComponentState<TContractState>) -> ContractAddress {
starknet::get_contract_address()
}

/// See `GovernorComponent::GovernorExecutionTrait::execute_operations`.
fn execute_operations(
ref self: GovernorComponentState<TContractState>,
proposal_id: felt252,
calls: Span<Call>,
description_hash: felt252
) {
for call in calls {
let Call { to, selector, calldata } = *call;
starknet::syscalls::call_contract_syscall(to, selector, calldata).unwrap_syscall();
};
}

/// See `GovernorComponent::GovernorExecutionTrait::queue_operations`.
fn queue_operations(
ref self: GovernorComponentState<TContractState>,
proposal_id: felt252,
calls: Span<Call>,
description_hash: felt252
) -> u64 {
0
}

/// See `GovernorComponent::GovernorExecutionTrait::proposal_needs_queuing`.
fn proposal_needs_queuing(
self: @GovernorComponentState<TContractState>, proposal_id: felt252
) -> bool {
false
}

/// See `GovernorComponent::GovernorExecutionTrait::cancel_operations`.
fn cancel_operations(
ref self: GovernorComponentState<TContractState>,
proposal_id: felt252,
description_hash: felt252
) {
self._cancel(proposal_id, description_hash);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.19.0
// (governance/governor/extensions/governor_counting_simple.cairo)

/// # GovernorCountingSimple Component
///
/// Extension of GovernorComponent for simple vote counting with three options.
#[starknet::component]
pub mod GovernorCountingSimpleComponent {
use crate::governor::GovernorComponent::{
InternalTrait, ComponentState as GovernorComponentState
};
use crate::governor::GovernorComponent;
use openzeppelin_introspection::src5::SRC5Component;
use starknet::ContractAddress;
use starknet::storage::{Map, StoragePathEntry, StorageMapReadAccess, StorageMapWriteAccess};
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};

type ProposalId = felt252;

#[storage]
pub struct Storage {
pub Governor_proposals_votes: Map<ProposalId, ProposalVote>,
}

/// Supported vote types.
#[derive(Drop, PartialEq, Debug)]
pub enum VoteType {
Against,
For,
Abstain
}

impl U8TryIntoVoteType of TryInto<u8, VoteType> {
fn try_into(self: u8) -> Option<VoteType> {
match self {
0 => Option::Some(VoteType::Against),
1 => Option::Some(VoteType::For),
2 => Option::Some(VoteType::Abstain),
_ => Option::None,
}
}
}

impl VoteTypeIntoU8 of Into<VoteType, u8> {
fn into(self: VoteType) -> u8 {
match self {
VoteType::Against => 0,
VoteType::For => 1,
VoteType::Abstain => 2
}
}
}

#[starknet::storage_node]
pub struct ProposalVote {
pub against_votes: u256,
pub for_votes: u256,
pub abstain_votes: u256,
pub has_voted: Map<ContractAddress, bool>
}

pub mod Errors {
pub const ALREADY_CAST_VOTE: felt252 = 'Already cast vote';
pub const INVALID_VOTE_TYPE: felt252 = 'Invalid vote type';
}

//
// Extensions
//

pub impl GovernorCounting<
TContractState,
+GovernorComponent::HasComponent<TContractState>,
+GovernorComponent::GovernorQuorumTrait<TContractState>,
+SRC5Component::HasComponent<TContractState>,
impl GovernorCountingSimple: HasComponent<TContractState>,
+Drop<TContractState>
> of GovernorComponent::GovernorCountingTrait<TContractState> {
/// See `GovernorComponent::GovernorCountingTrait::counting_mode`.
fn counting_mode(self: @GovernorComponentState<TContractState>) -> ByteArray {
return "support=bravo&quorum=for,abstain";
}

/// See `GovernorComponent::GovernorCountingTrait::count_vote`.
///
/// In this module, the support follows the `VoteType` enum (from Governor Bravo).
fn count_vote(
ref self: GovernorComponentState<TContractState>,
proposal_id: felt252,
account: ContractAddress,
support: u8,
total_weight: u256,
params: Span<felt252>
) -> u256 {
let mut contract = self.get_contract_mut();
let mut this_component = GovernorCountingSimple::get_component_mut(ref contract);

let proposal_votes = this_component.Governor_proposals_votes.entry(proposal_id);
assert(!proposal_votes.has_voted.read(account), Errors::ALREADY_CAST_VOTE);

proposal_votes.has_voted.write(account, true);

let support: VoteType = support.try_into().expect(Errors::INVALID_VOTE_TYPE);
match support {
VoteType::Against => {
let current_votes = proposal_votes.against_votes.read();
proposal_votes.against_votes.write(current_votes + total_weight);
},
VoteType::For => {
let current_votes = proposal_votes.for_votes.read();
proposal_votes.for_votes.write(current_votes + total_weight);
},
VoteType::Abstain => {
let current_votes = proposal_votes.abstain_votes.read();
proposal_votes.abstain_votes.write(current_votes + total_weight);
}
}
total_weight
}

/// See `GovernorComponent::GovernorCountingTrait::has_voted`.
fn has_voted(
self: @GovernorComponentState<TContractState>,
proposal_id: felt252,
account: ContractAddress
) -> bool {
let contract = self.get_contract();
let this_component = GovernorCountingSimple::get_component(contract);
let proposal_votes = this_component.Governor_proposals_votes.entry(proposal_id);

proposal_votes.has_voted.read(account)
}

/// See `GovernorComponent::GovernorCountingTrait::quorum_reached`.
fn quorum_reached(
self: @GovernorComponentState<TContractState>, proposal_id: felt252
) -> bool {
let contract = self.get_contract();
let this_component = GovernorCountingSimple::get_component(contract);

let proposal_votes = this_component.Governor_proposals_votes.entry(proposal_id);
let snapshot = self._proposal_snapshot(proposal_id);

self.quorum(snapshot) <= proposal_votes.for_votes.read()
+ proposal_votes.abstain_votes.read()
}

/// See `GovernorComponent::GovernorCountingTrait::vote_succeeded`.
///
/// In this module, the `for_votes` must be strictly over the `against_votes`.
fn vote_succeeded(
self: @GovernorComponentState<TContractState>, proposal_id: felt252
) -> bool {
let contract = self.get_contract();
let this_component = GovernorCountingSimple::get_component(contract);
let proposal_votes = this_component.Governor_proposals_votes.entry(proposal_id);

proposal_votes.for_votes.read() > proposal_votes.against_votes.read()
}
}
}
Loading

0 comments on commit f0edfaa

Please sign in to comment.