Skip to content

Commit

Permalink
Add custom proposal implementation
Browse files Browse the repository at this point in the history
Lacks setting of custom proposal configuration during deployment
  • Loading branch information
tensojka committed Apr 25, 2024
1 parent b97bfee commit f39edd3
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 14 deletions.
72 changes: 63 additions & 9 deletions src/proposals.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ trait IProposals<TContractState> {
fn get_user_voted(
self: @TContractState, user_address: ContractAddress, prop_id: felt252
) -> VoteStatus;
fn submit_custom_proposal(
ref self: TContractState, custom_proposal_type: u32, calldata: Span<felt252>
) -> u32;
}

#[starknet::component]
Expand All @@ -34,11 +37,13 @@ mod proposals {
use hash::LegacyHash;

use starknet::contract_address::ContractAddressZeroable;
use starknet::class_hash::ClassHashZeroable;
use starknet::get_block_info;
use starknet::get_block_timestamp;
use starknet::get_caller_address;
use starknet::BlockInfo;
use starknet::ContractAddress;
use starknet::ClassHash;
use starknet::contract_address_const;
use starknet::event::EventEmitter;
use starknet::get_contract_address;
Expand All @@ -51,6 +56,7 @@ mod proposals {
use governance::types::ContractType;
use governance::types::PropDetails;
use governance::types::VoteStatus;
use governance::types::CustomProposalConfig;
use governance::traits::IERC20Dispatcher;
use governance::traits::IERC20DispatcherTrait;
use governance::traits::get_governance_token_address_self;
Expand All @@ -67,6 +73,10 @@ mod proposals {
proposal_applied: LegacyMap::<felt252, felt252>, // should be Bool after migration
delegate_hash: LegacyMap::<ContractAddress, felt252>,
total_delegated_to: LegacyMap::<ContractAddress, u128>,
custom_proposal_type: LegacyMap::<u32, CustomProposalConfig>, // custom proposal type
custom_proposal_payload: LegacyMap::<
(u32, u32), felt252
> // mapping from prop_id and index to calldata
}

#[derive(starknet::Event, Drop)]
Expand Down Expand Up @@ -221,6 +231,17 @@ mod proposals {
.update_calldata(to_addr, new_amount, calldata_span, new_list, index + 1_usize);
}
}

fn assert_eligible_to_propose(self: @ComponentState<TContractState>) {
let user_address = get_caller_address();
let govtoken_addr = get_governance_token_address_self();
let caller_balance: u128 = IERC20Dispatcher { contract_address: govtoken_addr }
.balanceOf(user_address)
.low;
let total_supply = IERC20Dispatcher { contract_address: govtoken_addr }.totalSupply();
let res: u256 = (caller_balance * constants::NEW_PROPOSAL_QUORUM).into();
assert(total_supply < res, 'not enough tokens to submit');
}
}

#[embeddable_as(ProposalsImpl)]
Expand Down Expand Up @@ -275,15 +296,7 @@ mod proposals {
ref self: ComponentState<TContractState>, payload: felt252, to_upgrade: ContractType
) -> felt252 {
assert_correct_contract_type(to_upgrade);
let govtoken_addr = get_governance_token_address_self();
let caller = get_caller_address();
let caller_balance: u128 = IERC20Dispatcher { contract_address: govtoken_addr }
.balanceOf(caller)
.low;
let total_supply = IERC20Dispatcher { contract_address: govtoken_addr }.totalSupply();
let res: u256 = (caller_balance * constants::NEW_PROPOSAL_QUORUM)
.into(); // TODO use such multiplication that u128 * u128 = u256
assert(total_supply < res, 'not enough tokens to submit');
self.assert_eligible_to_propose();

let prop_id = self.get_free_prop_id_timestamp();
let prop_details = PropDetails { payload: payload, to_upgrade: to_upgrade.into() };
Expand All @@ -297,6 +310,47 @@ mod proposals {
prop_id
}

fn submit_custom_proposal(
ref self: ComponentState<TContractState>,
custom_proposal_type: u32,
mut calldata: Span<felt252>
) -> u32 {
let config: CustomProposalConfig = self.custom_proposal_type.read(custom_proposal_type);
assert(
config.target.is_non_zero(), 'custom prop classhash 0'
); // wrong custom proposal type?
assert(
config.selector.is_non_zero(), 'custom prop selector 0'
); // wrong custom proposal type?
self.assert_eligible_to_propose();

let prop_id_felt = self.get_free_prop_id_timestamp();
let prop_id: u32 = prop_id_felt.try_into().unwrap();
let payload = custom_proposal_type.into();
let prop_details = PropDetails {
payload, to_upgrade: 5
}; // to_upgrade = 5 – custom proposal type.
self.proposal_details.write(prop_id_felt, prop_details);

let current_timestamp: u64 = get_block_timestamp();
let end_timestamp: u64 = current_timestamp + constants::PROPOSAL_VOTING_SECONDS;
self.proposal_vote_end_timestamp.write(prop_id_felt, end_timestamp);
self.emit(Proposed { prop_id: prop_id_felt, payload, to_upgrade: 5 });

self.custom_proposal_payload.write((prop_id, 0), calldata.len().into());
let mut i: u32 = 1;
loop {
match calldata.pop_front() {
Option::Some(argument) => {
self.custom_proposal_payload.write((prop_id, i), *argument);
i += 1;
},
Option::None(()) => { break (); }
}
};
prop_id
}


// fn delegate_vote(
// ref self: ComponentState<TContractState>,
Expand Down
14 changes: 10 additions & 4 deletions src/types.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use starknet::SyscallResult;
use starknet::syscalls::storage_read_syscall;
use starknet::syscalls::storage_write_syscall;
use starknet::syscalls::{storage_read_syscall, storage_write_syscall, ClassHash};
use starknet::storage_address_from_base_and_offset;
use core::serde::Serde;

Expand All @@ -16,8 +15,15 @@ struct VoteCounts {
}

type BlockNumber = felt252;
type VoteStatus = felt252; // 0 = not voted, 1 = yay, -1 = nay
type VoteStatus = felt252; // 0 = not voted, 1 = yay, 2 = nay
type ContractType =
u64; // for Carmine 0 = amm, 1 = governance, 2 = CARM token, 3 = merkle tree root, 4 = no-op/signal vote
u64; // for Carmine 0 = amm, 1 = governance, 2 = CARM token, 3 = merkle tree root, 4 = no-op/signal vote, 5 = custom proposal
type OptionSide = felt252;
type OptionType = felt252;

#[derive(Copy, Drop, Serde, starknet::Store)]
struct CustomProposalConfig {
target: felt252, //class hash if library call, contract address if regular call
selector: felt252,
library_call: bool
}
41 changes: 40 additions & 1 deletion src/upgrades.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ trait IUpgrades<TContractState> {

#[starknet::component]
mod upgrades {
use core::result::ResultTrait;
use core::array::ArrayTrait;
use traits::TryInto;
use option::OptionTrait;
use traits::Into;
Expand All @@ -16,7 +18,7 @@ mod upgrades {
use starknet::ContractAddress;
use starknet::class_hash;

use governance::types::PropDetails;
use governance::types::{CustomProposalConfig, PropDetails};
use governance::contract::Governance;
use governance::contract::Governance::ContractState;

Expand Down Expand Up @@ -92,6 +94,43 @@ mod upgrades {
} else if (contract_type == 3) {
let mut airdrop_comp = get_dep_component_mut!(ref self, Airdrop);
airdrop_comp.merkle_root.write(impl_hash);
} else if (contract_type == 5) {
// custom proposal
let custom_proposal_type: u32 = impl_hash
.try_into()
.expect('custom prop type fit in u32');
let config: CustomProposalConfig = proposals_comp
.custom_proposal_type
.read(custom_proposal_type);

let prop_id_: u32 = prop_id.try_into().unwrap();
let mut calldata_len = proposals_comp
.custom_proposal_payload
.read((prop_id_, 0));
let mut calldata: Array<felt252> = ArrayTrait::new();
let mut i: u32 = 1;
while (calldata_len != 0) {
calldata
.append(proposals_comp.custom_proposal_payload.read((prop_id_, i)));
i += 1;
calldata_len -= 1;
};

if (config.library_call) {
let res = syscalls::library_call_syscall(
config.target.try_into().expect('unable to convert>classhash'),
config.selector,
calldata.span()
);
res.expect('libcall failed');
} else {
let res = syscalls::call_contract_syscall(
config.target.try_into().expect('unable to convert>addr'),
config.selector,
calldata.span()
);
res.expect('contract call failed');
}
} else {
assert(
contract_type == 4, 'invalid contract_type'
Expand Down

0 comments on commit f39edd3

Please sign in to comment.