From 489e24328af1d9d98d7c2bc83502f6e8fd70d5f3 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Thu, 11 Apr 2024 15:49:35 -0400 Subject: [PATCH 1/2] Allow describing proposals on-chain --- src/airdrop_claim_check.cairo | 8 ++++---- src/governor.cairo | 21 ++++++++++++++++++++- src/lib.cairo | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/airdrop_claim_check.cairo b/src/airdrop_claim_check.cairo index 4e02c06..29ee8a5 100644 --- a/src/airdrop_claim_check.cairo +++ b/src/airdrop_claim_check.cairo @@ -1,5 +1,5 @@ -use starknet::{ContractAddress}; use governance::airdrop::{IAirdropDispatcher}; +use starknet::{ContractAddress}; #[derive(Serde, Copy, Drop)] struct CheckParams { @@ -21,11 +21,11 @@ trait IAirdropClaimCheck { #[starknet::contract] mod AirdropClaimCheck { - use super::{IAirdropClaimCheck, IAirdropDispatcher, CheckParams, CheckResult}; - use governance::airdrop::{IAirdropDispatcherTrait}; - use governance::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; use core::array::{SpanTrait}; use core::option::{OptionTrait}; + use governance::airdrop::{IAirdropDispatcherTrait}; + use governance::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; + use super::{IAirdropClaimCheck, IAirdropDispatcher, CheckParams, CheckResult}; #[storage] struct Storage {} diff --git a/src/governor.cairo b/src/governor.cairo index 7ace3f2..a1f9f88 100644 --- a/src/governor.cairo +++ b/src/governor.cairo @@ -1,4 +1,5 @@ use core::array::{Array}; +use core::byte_array::{ByteArray}; use core::integer::{u128_safe_divmod}; use core::option::{Option, OptionTrait}; use core::traits::{Into, TryInto}; @@ -49,6 +50,9 @@ pub trait IGovernor { // Execute the given proposal. fn execute(ref self: TContractState, call: Call) -> Span; + // Attaches the given text to the proposal. Triggers an event containing the proposal description. + fn describe(ref self: TContractState, id: felt252, description: ByteArray); + // Get the configuration for this governor contract. fn get_staker(self: @TContractState) -> IStakerDispatcher; @@ -68,7 +72,7 @@ pub mod Governor { use starknet::{get_block_timestamp, get_caller_address, contract_address_const}; use super::{ IStakerDispatcher, ContractAddress, Array, IGovernor, Config, ProposalInfo, Call, - ExecutionState + ExecutionState, ByteArray }; @@ -79,6 +83,12 @@ pub mod Governor { pub call: Call, } + #[derive(starknet::Event, Drop)] + pub struct Described { + pub id: felt252, + pub description: ByteArray, + } + #[derive(starknet::Event, Drop)] pub struct Voted { pub id: felt252, @@ -101,6 +111,7 @@ pub mod Governor { #[event] enum Event { Proposed: Proposed, + Described: Described, Voted: Voted, Canceled: Canceled, Executed: Executed, @@ -183,6 +194,14 @@ pub mod Governor { id } + fn describe(ref self: ContractState, id: felt252, description: ByteArray) { + let proposal = self.proposals.read(id); + assert(proposal.proposer == get_caller_address(), 'NOT_PROPOSER'); + assert(proposal.execution_state.executed.is_zero(), 'ALREADY_EXECUTED'); + assert(proposal.execution_state.canceled.is_zero(),'CANCELED'); + self.emit(Described { id, description }); + } + fn vote(ref self: ContractState, id: felt252, yea: bool) { let mut proposal = self.proposals.read(id); diff --git a/src/lib.cairo b/src/lib.cairo index 2ee83c8..33a2ebd 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,5 +1,5 @@ -mod airdrop_claim_check; pub mod airdrop; +mod airdrop_claim_check; #[cfg(test)] pub(crate) mod airdrop_test; From ca530b4490022f50fa6e3280065029c5b93da88f Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Fri, 12 Apr 2024 10:17:12 -0400 Subject: [PATCH 2/2] add some unit tests --- src/governor.cairo | 7 +++--- src/governor_test.cairo | 49 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/governor.cairo b/src/governor.cairo index a1f9f88..b25c664 100644 --- a/src/governor.cairo +++ b/src/governor.cairo @@ -50,7 +50,7 @@ pub trait IGovernor { // Execute the given proposal. fn execute(ref self: TContractState, call: Call) -> Span; - // Attaches the given text to the proposal. Triggers an event containing the proposal description. + // Attaches the given text to the proposal. Simply emits an event containing the proposal description. fn describe(ref self: TContractState, id: felt252, description: ByteArray); // Get the configuration for this governor contract. @@ -83,7 +83,7 @@ pub mod Governor { pub call: Call, } - #[derive(starknet::Event, Drop)] + #[derive(starknet::Event, Drop, Debug, PartialEq)] pub struct Described { pub id: felt252, pub description: ByteArray, @@ -196,9 +196,10 @@ pub mod Governor { fn describe(ref self: ContractState, id: felt252, description: ByteArray) { let proposal = self.proposals.read(id); + assert(proposal.proposer.is_non_zero(), 'DOES_NOT_EXIST'); assert(proposal.proposer == get_caller_address(), 'NOT_PROPOSER'); assert(proposal.execution_state.executed.is_zero(), 'ALREADY_EXECUTED'); - assert(proposal.execution_state.canceled.is_zero(),'CANCELED'); + assert(proposal.execution_state.canceled.is_zero(), 'PROPOSAL_CANCELED'); self.emit(Described { id, description }); } diff --git a/src/governor_test.cairo b/src/governor_test.cairo index 3de0d35..806e231 100644 --- a/src/governor_test.cairo +++ b/src/governor_test.cairo @@ -21,7 +21,8 @@ use governance::timelock_test::{single_call, transfer_call, deploy as deploy_tim use starknet::account::{Call}; use starknet::{ get_contract_address, syscalls::deploy_syscall, ClassHash, contract_address_const, - ContractAddress, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} + ContractAddress, get_block_timestamp, + testing::{set_block_timestamp, set_contract_address, pop_log} }; @@ -250,6 +251,52 @@ fn test_anyone_can_vote() { assert_eq!(proposal.nay, 0); } +#[test] +fn test_describe_proposal_successful() { + let (staker, token, governor, _config) = setup(); + let id = create_proposal(governor, token, staker); + + set_contract_address(proposer()); + governor + .describe( + id, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ); + pop_log::(governor.contract_address).unwrap(); + assert_eq!( + pop_log::(governor.contract_address).unwrap(), + Governor::Described { + id, + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + } + ); +} + +#[test] +#[should_panic(expected: ('NOT_PROPOSER', 'ENTRYPOINT_FAILED'))] +fn test_describe_proposal_fails_for_unknown_proposal() { + let (staker, token, governor, _config) = setup(); + let id = create_proposal(governor, token, staker); + governor.describe(id, "I am not the proposer"); +} + +#[test] +#[should_panic(expected: ('PROPOSAL_CANCELED', 'ENTRYPOINT_FAILED'))] +fn test_describe_proposal_fails_if_canceled() { + let (staker, token, governor, _config) = setup(); + let id = create_proposal(governor, token, staker); + set_contract_address(proposer()); + governor.cancel(id); + governor.describe(id, "This proposal is canceled"); +} + +#[test] +#[should_panic(expected: ('DOES_NOT_EXIST', 'ENTRYPOINT_FAILED'))] +fn test_describe_proposal_fails_if_not_proposer() { + let (_staker, _token, governor, _config) = setup(); + governor.describe(123, "This proposal does not exist"); +} + #[test] fn test_vote_no_staking_after_period_starts() { let (staker, token, governor, config) = setup();