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 1 commit
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,47 @@
/// 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, IQuorum};
pscott marked this conversation as resolved.
Show resolved Hide resolved
use sx::types::{Proposal, ProposalStatus};
use sx::execution_strategies::simple_quorum::SimpleQuorumExecutionStrategy;
pscott marked this conversation as resolved.
Show resolved Hide resolved
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'
}
}
}
2 changes: 2 additions & 0 deletions starknet/src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ mod struct_hash;

mod single_slot_proof;

mod simple_majority;

mod signatures;

mod stark_eip712;
Expand Down
179 changes: 179 additions & 0 deletions starknet/src/utils/simple_majority.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
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`.
/// A proposal will return `VotingPeriodAccepted` if `min_end_timestamp` has been reached and `votes_for > votes_against`.
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.min_end_timestamp {
ProposalStatus::VotingPeriod(())
} 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

if accepted {
ProposalStatus::VotingPeriodAccepted(())
} else {
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;
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 shortcut_accepted() {
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::VotingPeriodAccepted(()), 'failed shortcut_accepted');
}

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

#[test]
#[available_gas(10000000)]
fn shortcut_only_againsts() {
let mut proposal = ProposalDefault::default();
proposal.max_end_timestamp = 10;
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::VotingPeriod(()), 'failed shortcut_only_againsts');
}

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

#[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');
}
}