Skip to content

Commit

Permalink
feat: proposal_status as struct
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveVodrazka committed Dec 14, 2023
1 parent 141800e commit a74c50a
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 136 deletions.
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ build:
rm -rf target/
scarb build

deploy: build
starknet declare --contract target/release/governance_Governance.json --account version_11
# Declares class hash, the hash is then printed into terminal.
# Following ENV variables must be set:
# STARKNET_ACCOUNT - path to account file
# STARKNET_KEYSTORE - path to keystore file
# STARKNET_RPC - RPC node URL - network will be selected based on RPC network
_declare:
starkli declare target/dev/governance_Governance.contract_class.json


declare: build declare
58 changes: 26 additions & 32 deletions src/contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@
// When Components arrive in Cairo 2.?, it will be refactored to take advantage of them. Random change to rerun CI

use starknet::ContractAddress;
use governance::types::{ContractType, PropDetails};
use governance::types::{ContractType, PropDetails, PropStatus, VoteCount};

#[starknet::interface]
trait IGovernance<TContractState> {
// PROPOSALS

fn vote(ref self: TContractState, prop_id: felt252, opinion: felt252);
fn get_proposal_details(self: @TContractState, prop_id: felt252) -> PropDetails;
fn get_vote_counts(self: @TContractState, prop_id: felt252) -> (u128, u128);
fn submit_proposal(
ref self: TContractState, impl_hash: felt252, to_upgrade: ContractType
) -> felt252;
fn get_proposal_status(self: @TContractState, prop_id: felt252) -> felt252;
fn vote(ref self: TContractState, prop_id: u32, opinion: bool);
fn get_proposal_details(self: @TContractState, prop_id: u32) -> PropDetails;
fn get_vote_counts(self: @TContractState, prop_id: u32) -> VoteCount;
fn submit_proposal(ref self: TContractState, impl_hash: felt252, to_upgrade: u8) -> u32;
fn get_proposal_status(self: @TContractState, prop_id: u32) -> PropStatus;

// UPGRADES

fn get_governance_token_address(self: @TContractState) -> ContractAddress;
fn get_amm_address(self: @TContractState) -> ContractAddress;
fn apply_passed_proposal(ref self: TContractState, prop_id: felt252);
fn apply_passed_proposal(ref self: TContractState, prop_id: u32);

// AIRDROPS

Expand All @@ -34,11 +32,10 @@ trait IGovernance<TContractState> {

#[starknet::contract]
mod Governance {
use governance::types::BlockNumber;
use governance::types::VoteStatus;
use governance::types::{
BlockNumber, ContractType, PropDetails, PropStatus, VoteStatus, VoteCount
};
use governance::proposals::Proposals;
use governance::types::ContractType;
use governance::types::PropDetails;
use governance::upgrades::Upgrades;
use governance::options::Options;
use governance::airdrop::airdrop as airdrop_component;
Expand All @@ -53,13 +50,12 @@ mod Governance {

#[storage]
struct Storage {
proposal_details: LegacyMap::<felt252, PropDetails>,
proposal_vote_ends: LegacyMap::<felt252, BlockNumber>,
proposal_vote_end_timestamp: LegacyMap::<felt252, u64>,
proposal_voted_by: LegacyMap::<(felt252, ContractAddress), VoteStatus>,
proposal_total_yay: LegacyMap::<felt252, felt252>,
proposal_total_nay: LegacyMap::<felt252, felt252>,
proposal_applied: LegacyMap::<felt252, felt252>, // should be Bool after migration
proposal_details: LegacyMap::<u32, PropDetails>,
proposal_vote_ends: LegacyMap::<u32, BlockNumber>,
proposal_vote_end_timestamp: LegacyMap::<u32, u64>,
proposal_voted_by: LegacyMap::<(u32, ContractAddress), Option<VoteStatus>>,
proposal_total: LegacyMap::<u32, VoteCount>,
proposal_applied: LegacyMap::<u32, felt252>, // should be Bool after migration
proposal_initializer_run: LegacyMap::<u64, bool>,
investor_voting_power: LegacyMap::<ContractAddress, felt252>,
total_investor_distributed_power: felt252,
Expand All @@ -75,16 +71,16 @@ mod Governance {

#[derive(starknet::Event, Drop)]
struct Proposed {
prop_id: felt252,
prop_id: u32,
payload: felt252,
to_upgrade: ContractType
to_upgrade: u8,
}

#[derive(starknet::Event, Drop)]
struct Voted {
prop_id: felt252,
prop_id: u32,
voter: ContractAddress,
opinion: VoteStatus
opinion: bool
}

#[derive(starknet::Event, Drop)]
Expand All @@ -105,28 +101,26 @@ mod Governance {
impl Governance of super::IGovernance<ContractState> {
// PROPOSALS

fn get_proposal_details(self: @ContractState, prop_id: felt252) -> PropDetails {
fn get_proposal_details(self: @ContractState, prop_id: u32) -> PropDetails {
Proposals::get_proposal_details(prop_id)
}

// This should ideally return VoteCounts, but it seems like structs can't be returned from
// C1.0 external fns as they can't be serialized
// Actually it can, TODO do the same as I did with PropDetails for this
fn get_vote_counts(self: @ContractState, prop_id: felt252) -> (u128, u128) {
fn get_vote_counts(self: @ContractState, prop_id: u32) -> VoteCount {
Proposals::get_vote_counts(prop_id)
}

fn submit_proposal(
ref self: ContractState, impl_hash: felt252, to_upgrade: ContractType
) -> felt252 {
fn submit_proposal(ref self: ContractState, impl_hash: felt252, to_upgrade: u8) -> u32 {
Proposals::submit_proposal(impl_hash, to_upgrade)
}

fn vote(ref self: ContractState, prop_id: felt252, opinion: felt252) {
fn vote(ref self: ContractState, prop_id: u32, opinion: bool) {
Proposals::vote(prop_id, opinion)
}

fn get_proposal_status(self: @ContractState, prop_id: felt252) -> felt252 {
fn get_proposal_status(self: @ContractState, prop_id: u32) -> PropStatus {
Proposals::get_proposal_status(prop_id)
}

Expand All @@ -140,7 +134,7 @@ mod Governance {
self.amm_address.read()
}

fn apply_passed_proposal(ref self: ContractState, prop_id: felt252) {
fn apply_passed_proposal(ref self: ContractState, prop_id: u32) {
Upgrades::apply_passed_proposal(prop_id)
}

Expand Down
120 changes: 53 additions & 67 deletions src/proposals.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod Proposals {
use core::traits::Destruct;
use governance::contract::IGovernance;
use traits::TryInto;
use option::OptionTrait;
Expand All @@ -24,8 +25,7 @@ mod Proposals {
use starknet::class_hash::class_hash_try_from_felt252;
use starknet::contract_address::contract_address_to_felt252;

use governance::contract::Governance::proposal_total_yayContractMemberStateTrait;
use governance::contract::Governance::proposal_total_nayContractMemberStateTrait;
use governance::contract::Governance::proposal_totalContractMemberStateTrait;
use governance::contract::Governance::proposal_vote_endsContractMemberStateTrait;
use governance::contract::Governance::proposal_vote_end_timestampContractMemberStateTrait;
use governance::contract::Governance::delegate_hashContractMemberStateTrait;
Expand All @@ -35,33 +35,28 @@ mod Proposals {
use governance::contract::Governance::ContractState;
use governance::contract::Governance::unsafe_new_contract_state;
use governance::contract::Governance;
use governance::types::BlockNumber;
use governance::types::ContractType;
use governance::types::PropDetails;
use governance::types::{
BlockNumber, ContractType, PropDetails, PropStatus, VoteCount, VoteStatus
};
use governance::traits::IERC20Dispatcher;
use governance::traits::IERC20DispatcherTrait;
use governance::constants;

fn get_vote_counts(prop_id: felt252) -> (u128, u128) {
fn get_vote_counts(prop_id: u32) -> VoteCount {
let state: ContractState = Governance::unsafe_new_contract_state();

let yay = state.proposal_total_yay.read(prop_id);
let nay = state.proposal_total_nay.read(prop_id);

(yay.try_into().unwrap(), nay.try_into().unwrap())
state.proposal_total.read(prop_id)
}

fn get_proposal_details(prop_id: felt252) -> PropDetails {
fn get_proposal_details(prop_id: u32) -> PropDetails {
let state = Governance::unsafe_new_contract_state();
state.proposal_details.read(prop_id)
}

fn assert_correct_contract_type(contract_type: ContractType) {
let contract_type_u: u64 = contract_type.try_into().unwrap();
assert(contract_type_u <= 4, 'invalid contract type')
fn assert_correct_contract_type(contract_type: u8) {
assert(contract_type <= 4, 'invalid contract type')
}

fn assert_voting_in_progress(prop_id: felt252) {
fn assert_voting_in_progress(prop_id: u32) {
let state = Governance::unsafe_new_contract_state();
let end_timestamp: u64 = state.proposal_vote_end_timestamp.read(prop_id);
assert(end_timestamp != 0, 'prop_id not found');
Expand All @@ -75,11 +70,11 @@ mod Proposals {
u256 { low, high }
}

fn get_free_prop_id() -> felt252 {
fn get_free_prop_id() -> u32 {
_get_free_prop_id(0)
}

fn _get_free_prop_id(currid: felt252) -> felt252 {
fn _get_free_prop_id(currid: u32) -> u32 {
let state = Governance::unsafe_new_contract_state();
let res = state.proposal_vote_ends.read(currid);
if res == 0 {
Expand All @@ -89,11 +84,11 @@ mod Proposals {
}
}

fn get_free_prop_id_timestamp() -> felt252 {
fn get_free_prop_id_timestamp() -> u32 {
_get_free_prop_id_timestamp(0)
}

fn _get_free_prop_id_timestamp(currid: felt252) -> felt252 {
fn _get_free_prop_id_timestamp(currid: u32) -> u32 {
let state = Governance::unsafe_new_contract_state();
let res = state.proposal_vote_end_timestamp.read(currid);
if res == 0 {
Expand All @@ -103,7 +98,7 @@ mod Proposals {
}
}

fn submit_proposal(payload: felt252, to_upgrade: ContractType) -> felt252 {
fn submit_proposal(payload: felt252, to_upgrade: u8) -> u32 {
assert_correct_contract_type(to_upgrade);
let mut state = Governance::unsafe_new_contract_state();
let govtoken_addr = state.get_governance_token_address();
Expand All @@ -122,7 +117,7 @@ mod Proposals {
state.proposal_details.write(prop_id, prop_details);

let current_timestamp: u64 = get_block_timestamp();
let end_timestamp: u64 = current_timestamp + constants::PROPOSAL_VOTING_SECONDS;
let end_timestamp: u64 = current_timestamp + 600; // ten minutes
state.proposal_vote_end_timestamp.write(prop_id, end_timestamp);

state.emit(Governance::Proposed { prop_id, payload, to_upgrade });
Expand Down Expand Up @@ -236,23 +231,13 @@ mod Proposals {
}


fn vote(prop_id: felt252, opinion: felt252) {
// Checks
assert((opinion == 1) | (opinion == 2), 'opinion must be either 1 or 2');
let mut actual_opinion = 0;
if opinion == 2 {
actual_opinion = constants::MINUS_ONE;
} else {
actual_opinion = 1;
}

fn vote(prop_id: u32, opinion: bool) {
let mut state = Governance::unsafe_new_contract_state();

let gov_token_addr = state.get_governance_token_address();
let caller_addr = get_caller_address();
let curr_vote_status: felt252 = state.proposal_voted_by.read((prop_id, caller_addr));
// TODO allow override of previous vote
assert(curr_vote_status == 0, 'already voted');
let vote_status: Option<VoteStatus> = state.proposal_voted_by.read((prop_id, caller_addr));

assert(vote_status.is_none(), 'already voted');

let caller_balance_u256: u256 = IERC20Dispatcher { contract_address: gov_token_addr }
.balanceOf(caller_addr);
Expand All @@ -266,28 +251,34 @@ mod Proposals {

assert_voting_in_progress(prop_id);

// Cast vote
state.proposal_voted_by.write((prop_id, caller_addr), actual_opinion);
if actual_opinion == constants::MINUS_ONE {
let curr_votes: u128 = state.proposal_total_nay.read(prop_id).try_into().unwrap();
let new_votes: u128 = curr_votes + caller_voting_power;
assert(new_votes >= 0, 'new_votes must be non-negative');
state.proposal_total_nay.write(prop_id, new_votes.into());
let current_vote_count = state.proposal_total.read(prop_id);

if opinion {
// Vote YAY
state.proposal_voted_by.write((prop_id, caller_addr), Option::Some(VoteStatus::Yay));
let new_vote_count = VoteCount {
yay: current_vote_count.yay + caller_voting_power, nay: current_vote_count.nay
};
assert(new_vote_count.yay >= 0, 'new_votes must be non-negative');
state.proposal_total.write(prop_id, new_vote_count);
} else {
let curr_votes: u128 = state.proposal_total_yay.read(prop_id).try_into().unwrap();
let new_votes: u128 = curr_votes + caller_voting_power;
assert(new_votes >= 0, 'new_votes must be non-negative');
state.proposal_total_yay.write(prop_id, new_votes.into());
// Vote NAY
state.proposal_voted_by.write((prop_id, caller_addr), Option::Some(VoteStatus::Nay));
let new_vote_count = VoteCount {
yay: current_vote_count.yay, nay: current_vote_count.nay + caller_voting_power
};
assert(current_vote_count.nay >= 0, 'new_votes must be non-negative');
state.proposal_total.write(prop_id, new_vote_count);
}

state.emit(Governance::Voted { prop_id: prop_id, voter: caller_addr, opinion: opinion });
}


fn check_proposal_passed_express(prop_id: felt252) -> u8 {
fn check_proposal_passed_express(prop_id: u32) -> PropStatus {
let state = Governance::unsafe_new_contract_state();
let gov_token_addr = state.get_governance_token_address();
let yay_tally_felt: felt252 = state.proposal_total_yay.read(prop_id);
let yay_tally: u128 = yay_tally_felt.try_into().unwrap();
let total = state.proposal_total.read(prop_id);
let total_eligible_votes_from_tokenholders_u256: u256 = IERC20Dispatcher {
contract_address: gov_token_addr
}
Expand All @@ -306,14 +297,14 @@ mod Proposals {
let minimum_for_express: u128 = total_eligible_votes_from_tokenholders / 2;

// Check if yay_tally >= minimum_for_express
if yay_tally >= minimum_for_express {
1
if total.yay >= minimum_for_express {
PropStatus { code: 1, status: 'accepted expressly' }
} else {
0
PropStatus { code: 2, status: 'voting still in progress' }
}
}

fn get_proposal_status(prop_id: felt252) -> felt252 {
fn get_proposal_status(prop_id: u32) -> PropStatus {
let state = Governance::unsafe_new_contract_state();

let end_timestamp: u64 = state.proposal_vote_end_timestamp.read(prop_id);
Expand All @@ -324,11 +315,9 @@ mod Proposals {
}

let gov_token_addr = state.get_governance_token_address();
let nay_tally_felt: felt252 = state.proposal_total_nay.read(prop_id);
let yay_tally_felt: felt252 = state.proposal_total_yay.read(prop_id);
let nay_tally: u128 = nay_tally_felt.try_into().unwrap();
let yay_tally: u128 = yay_tally_felt.try_into().unwrap();
let total_tally: u128 = yay_tally + nay_tally;
let vote_count = state.proposal_total.read(prop_id);
let total_tally: u128 = vote_count.yay + vote_count.nay;

// Here we multiply by 100 as the constant QUORUM is in percent.
// If QUORUM = 10, quorum was not met if (total_tally*100) < (total_eligible * 10).
let total_tally_multiplied = total_tally * 100;
Expand All @@ -339,18 +328,15 @@ mod Proposals {
let total_eligible_votes: u128 = total_eligible_votes_u256.low;

let quorum_threshold: u128 = total_eligible_votes * constants::QUORUM;
if total_tally_multiplied < quorum_threshold {
return constants::MINUS_ONE; // didn't meet quorum
}

if yay_tally == nay_tally {
return constants::MINUS_ONE; // yay_tally = nay_tally
if total_tally_multiplied < quorum_threshold {
return PropStatus { status: 'did not meet quorum', code: 0 }; // didn't meet quorum
}

if yay_tally > nay_tally {
return 1; // yay_tally > nay_tally
if vote_count.yay > vote_count.nay {
return PropStatus { status: 'passed', code: 1 }; // didn't meet quorum
} else {
return constants::MINUS_ONE; // yay_tally < nay_tally
return PropStatus { status: 'rejected', code: 0 }; // didn't meet quorum
}
}
}
Loading

0 comments on commit a74c50a

Please sign in to comment.