Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: simple majority execution #534

Merged
merged 4 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions starknet/src/execution_strategies.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod vanilla;

mod no_execution_simple_majority;
mod simple_quorum;

mod eth_relayer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/// Execution strategy that will not execute anything but ensure that the
/// proposal is in the status `Accepted` or `VotingPeriodAccepted` by following
/// the `SimpleMajority` rule (`votes_for > votes_against`).
#[starknet::contract]
mod NoExecutionSimpleMajorityExecutionStrategy {
use sx::interfaces::{IExecutionStrategy};
use sx::types::{Proposal, ProposalStatus};
use sx::utils::simple_majority;

#[storage]
struct Storage {}

#[external(v0)]
impl NoExecutionSimpleMajorityExecutionStrategy of IExecutionStrategy<ContractState> {
fn execute(
ref self: ContractState,
proposal: Proposal,
votes_for: u256,
votes_against: u256,
votes_abstain: u256,
payload: Array<felt252>
) {
let proposal_status = self
.get_proposal_status(proposal, votes_for, votes_against, votes_abstain,);
assert(
(proposal_status == ProposalStatus::Accepted(()))
| (proposal_status == ProposalStatus::VotingPeriodAccepted(())),
'Invalid Proposal Status'
);
}

fn get_proposal_status(
self: @ContractState,
proposal: Proposal,
votes_for: u256,
votes_against: u256,
votes_abstain: u256,
) -> ProposalStatus {
simple_majority::get_proposal_status(@proposal, votes_for, votes_against, votes_abstain)
}

fn get_strategy_type(self: @ContractState) -> felt252 {
'NoExecutionSimpleMajority'
}
}
}
22 changes: 6 additions & 16 deletions starknet/src/utils.cairo
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
mod bits;

mod constants;

mod eip712;
mod endian;
mod into;

mod keccak;
mod legacy_hash;

mod math;

mod merkle;

mod proposition_power;

mod struct_hash;

mod simple_majority;
mod single_slot_proof;

mod eip712;

mod endian;

mod keccak;

mod stark_eip712;
mod struct_hash;


// TODO: proper component syntax will have a better way to do this
mod reinitializable;
Expand Down
137 changes: 137 additions & 0 deletions starknet/src/utils/simple_majority.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use starknet::info;
use sx::types::{Proposal, FinalizationStatus, ProposalStatus};

/// Returns the status of a proposal, according to a 'Simple Majority' rule.
/// 'Simple Majority' is defined like so: a proposal is accepted if there are more `votes_for` than `votes_against`.
/// So, a proposal will return Accepted if `max_end_timestamp` has been reached, and `votes_for > votes_agasint`.
fn get_proposal_status(
proposal: @Proposal, votes_for: u256, votes_against: u256, votes_abstain: u256,
) -> ProposalStatus {
let accepted = votes_for > votes_against;

let timestamp = info::get_block_timestamp().try_into().unwrap();
if *proposal.finalization_status == FinalizationStatus::Cancelled(()) {
ProposalStatus::Cancelled(())
} else if *proposal.finalization_status == FinalizationStatus::Executed(()) {
ProposalStatus::Executed(())
} else if timestamp < *proposal.start_timestamp {
ProposalStatus::VotingDelay(())
} else if timestamp < *proposal.max_end_timestamp {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldnt have a VotingPeriodAccepted status with the strategy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think just ignore min_end_timestamp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm so the rationale behind that was:

  • those who don't want the early end can set min/max to the same value
  • those who want to have an early end (for whatever reason) can still have it without having to write another strategy

Copy link
Contributor

@Orland0x Orland0x Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is min/max are space params they cant be set just for proposals with this strategy.

The goal of a 'simple majority' strategy is to find the majority of VP over the voting period. i dont think it would ever make sense to have an early end. So if spaces are using this execution strategy in combination with others, they would need to update space params often which would be annoying/ error prone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough ! f6ac7e0

ProposalStatus::VotingPeriod(())
} else if accepted {
ProposalStatus::Accepted(())
} else {
ProposalStatus::Rejected(())
}
}

#[cfg(test)]
mod tests {
use super::{get_proposal_status};
use sx::types::{Proposal, proposal::ProposalDefault, FinalizationStatus, ProposalStatus};

#[test]
#[available_gas(10000000)]
fn cancelled() {
let mut proposal = ProposalDefault::default();
proposal.finalization_status = FinalizationStatus::Cancelled(());
let votes_for = 0;
let votes_against = 0;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::Cancelled(()), 'failed cancelled');
}

#[test]
#[available_gas(10000000)]
fn executed() {
let mut proposal = ProposalDefault::default();
proposal.finalization_status = FinalizationStatus::Executed(());
let votes_for = 0;
let votes_against = 0;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::Executed(()), 'failed executed');
}

#[test]
#[available_gas(10000000)]
fn voting_delay() {
let mut proposal = ProposalDefault::default();
proposal.start_timestamp = 42424242;
let votes_for = 0;
let votes_against = 0;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::VotingDelay(()), 'failed voting_delay');
}

#[test]
#[available_gas(10000000)]
fn voting_period() {
let mut proposal = ProposalDefault::default();
proposal.min_end_timestamp = 42424242;
proposal.max_end_timestamp = proposal.min_end_timestamp + 1;
let votes_for = 0;
let votes_against = 0;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::VotingPeriod(()), 'failed min_end_timestamp');
}

#[test]
#[available_gas(10000000)]
fn early_end_does_not_work() {
let mut proposal = ProposalDefault::default();
proposal.max_end_timestamp = 10;
let votes_for = 1;
let votes_against = 0;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::VotingPeriod(()), 'failed shortcut_accepted');
}

#[test]
#[available_gas(10000000)]
fn balanced() {
let mut proposal = ProposalDefault::default();
let votes_for = 42;
let votes_against = 42;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::Rejected(()), 'failed balanced');
}

#[test]
#[available_gas(10000000)]
fn accepted() {
let mut proposal = ProposalDefault::default();
let votes_for = 10;
let votes_against = 9;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::Accepted(()), 'failed accepted');
}

#[test]
#[available_gas(10000000)]
fn accepted_with_abstains() {
let mut proposal = ProposalDefault::default();
let votes_for = 2;
let votes_against = 1;
let votes_abstain = 10;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::Accepted(()), 'failed accepted abstains');
}

#[test]
#[available_gas(10000000)]
fn rejected_only_againsts() {
let mut proposal = ProposalDefault::default();
let votes_for = 0;
let votes_against = 1;
let votes_abstain = 0;
let result = get_proposal_status(@proposal, votes_for, votes_against, votes_abstain);
assert(result == ProposalStatus::Rejected(()), 'failed rejected');
}
}