Skip to content

Commit

Permalink
version bump
Browse files Browse the repository at this point in the history
  • Loading branch information
moodysalem committed Sep 4, 2024
1 parent 51a5123 commit 179ded8
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scarb 2.6.3
scarb 2.8.1
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Contracts in this repository are designed so that they may be used together _or_

- Users call `Token#approve(staker, stake_amount)`, then `Staker#stake_amount(delegate, stake_amount)` to stake and delegate their tokens to other addresses
- Users call `Staker#withdraw_amount(delegate, recipient, amount)` to remove part or all of their delegation
- The average delegation weight is computable over *any* historical period
- The average delegation weight is computable over _any_ historical period
- The contract has no owner, and cannot be updated nor configured

#### Governor
Expand Down
9 changes: 6 additions & 3 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ name = "governance"
version = "0.1.0"
description = "Contracts for governance of Starknet-native protocols"
homepage = "https://ekubo.org"
cairo-version = "2.6.3"
edition = '2023_11'
cairo-version = "2.8.0"
edition = '2024_07'

[dependencies]
starknet = "=2.6.3"
starknet = ">=2.7.0"

[[target.starknet-contract]]
allowed-libfuncs-list.name = "audited"
casm = true

[tool.fmt]
sort-module-level-items = true

[dev-dependencies]
cairo_test = "2.8.0"
85 changes: 43 additions & 42 deletions src/airdrop.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ pub trait IAirdrop<TContractState> {
fn get_config(self: @TContractState) -> Config;

// Claims the given allotment of tokens.
// Because this method is idempotent, it does not revert in case of a second submission of the same claim.
// Because this method is idempotent, it does not revert in case of a second submission of the
// same claim.
// This makes it simpler to batch many claims together in a single transaction.
// Returns true if the claim was processed. Returns false if the claim was already claimed.
// Panics if the proof is invalid.
fn claim(ref self: TContractState, claim: Claim, proof: Span<felt252>) -> bool;

// Claims the batch of up to 128 claims that must be aligned with a single bitmap, i.e. the id of the first must be a multiple of 128
// and the claims should be sequentially in order. The proof verification is optimized in this method.
// Claims the batch of up to 128 claims that must be aligned with a single bitmap, i.e. the id
// of the first must be a multiple of 128 and the claims should be sequentially in order. The
// proof verification is optimized in this method.
// Returns the number of claims that were executed.
fn claim_128(
ref self: TContractState, claims: Span<Claim>, remaining_proof: Span<felt252>
Expand All @@ -57,7 +59,12 @@ pub mod Airdrop {
use core::num::traits::zero::{Zero};
use governance::interfaces::erc20::{IERC20DispatcherTrait};
use governance::utils::exp2::{exp2};
use starknet::storage::{
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerWriteAccess,
StoragePointerReadAccess
};
use starknet::{get_block_timestamp, get_contract_address};

use super::{Config, IAirdrop, ContractAddress, Claim, IERC20Dispatcher};

pub fn hash_function(a: felt252, b: felt252) -> felt252 {
Expand All @@ -69,7 +76,8 @@ pub mod Airdrop {
}
}

// computes the pedersen root of a merkle tree by combining the current node with each sibling up the tree
// computes the pedersen root of a merkle tree by combining the current node with each sibling
// up the tree
pub fn compute_pedersen_root(current: felt252, mut proof: Span<felt252>) -> felt252 {
match proof.pop_front() {
Option::Some(proof_element) => {
Expand All @@ -83,7 +91,7 @@ pub mod Airdrop {
struct Storage {
token: IERC20Dispatcher,
config: Config,
claimed_bitmap: LegacyMap<u64, u128>,
claimed_bitmap: Map<u64, u128>,
}

#[derive(Drop, starknet::Event)]
Expand Down Expand Up @@ -121,35 +129,28 @@ pub mod Airdrop {

let mut last_claim_id: Option<u64> = Option::None;

while let Option::Some(claim) = claims
.pop_front() {
if let Option::Some(last_id) = last_claim_id {
assert(last_id == (*claim.id - 1), 'SEQUENTIAL');
};

claim_hashes.append(hash_claim(*claim));
last_claim_id = Option::Some(*claim.id);
while let Option::Some(claim) = claims.pop_front() {
if let Option::Some(last_id) = last_claim_id {
assert(last_id == (*claim.id - 1), 'SEQUENTIAL');
};

claim_hashes.append(hash_claim(*claim));
last_claim_id = Option::Some(*claim.id);
};

// will eventually contain an array of length 1
let mut current_layer: Span<felt252> = claim_hashes.span();

while current_layer
.len()
.is_non_one() {
let mut next_layer: Array<felt252> = array![];

while let Option::Some(hash) = current_layer
.pop_front() {
next_layer
.append(
hash_function(*hash, *current_layer.pop_front().unwrap_or(hash))
);
};
while current_layer.len().is_non_one() {
let mut next_layer: Array<felt252> = array![];

current_layer = next_layer.span();
while let Option::Some(hash) = current_layer.pop_front() {
next_layer.append(hash_function(*hash, *current_layer.pop_front().unwrap_or(hash)));
};

current_layer = next_layer.span();
};

*current_layer.pop_front().unwrap()
}

Expand Down Expand Up @@ -192,7 +193,8 @@ pub mod Airdrop {
assert(!claims.is_empty(), 'CLAIMS_EMPTY');

// groups that cross bitmap boundaries should just make multiple calls
// this code already reduces the number of pedersens in the verification by a factor of ~7
// this code already reduces the number of pedersens in the verification by a factor of
// ~7
let (word, index_u64) = DivRem::div_rem(*claims.at(0).id, BITMAP_SIZE);
assert(index_u64 == 0, 'FIRST_CLAIM_MUST_BE_MULT_128');

Expand All @@ -210,17 +212,16 @@ pub mod Airdrop {
let mut index: u8 = 0;
let mut unclaimed: Array<Claim> = array![];

while let Option::Some(claim) = claims
.pop_front() {
let already_claimed = (bitmap & exp2(index)).is_non_zero();
while let Option::Some(claim) = claims.pop_front() {
let already_claimed = (bitmap & exp2(index)).is_non_zero();

if !already_claimed {
bitmap = bitmap | exp2(index);
unclaimed.append(*claim);
}
if !already_claimed {
bitmap = bitmap | exp2(index);
unclaimed.append(*claim);
}

index += 1;
};
index += 1;
};

self.claimed_bitmap.write(word, bitmap);

Expand All @@ -229,13 +230,13 @@ pub mod Airdrop {
// the event emittance and transfers are separated from the above to prevent re-entrance
let token = self.token.read();

while let Option::Some(claim) = unclaimed
.pop_front() {
token.transfer(claim.claimee, claim.amount.into());
self.emit(Claimed { claim });
};
while let Option::Some(claim) = unclaimed.pop_front() {
token.transfer(claim.claimee, claim.amount.into());
self.emit(Claimed { claim });
};

// never fails because we assert claims length at the beginning so we know it's less than 128
// never fails because we assert claims length at the beginning so we know it's less
// than 128
num_claimed.try_into().unwrap()
}

Expand Down
24 changes: 11 additions & 13 deletions src/airdrop_claim_check.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use governance::airdrop::{IAirdropDispatcher};
use starknet::{ContractAddress};

#[derive(Serde, Copy, Drop)]
struct CheckParams {
Expand All @@ -23,7 +22,7 @@ trait IAirdropClaimCheck<TContractState> {
mod AirdropClaimCheck {
use governance::airdrop::{IAirdropDispatcherTrait};
use governance::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use super::{IAirdropClaimCheck, IAirdropDispatcher, CheckParams, CheckResult};
use super::{IAirdropClaimCheck, CheckParams, CheckResult};

#[storage]
struct Storage {}
Expand All @@ -33,18 +32,17 @@ mod AirdropClaimCheck {
fn check(self: @ContractState, mut claims: Span<CheckParams>) -> Span<CheckResult> {
let mut result: Array<CheckResult> = array![];

while let Option::Some(claim_check) = claims
.pop_front() {
let token = IERC20Dispatcher {
contract_address: (*claim_check.airdrop).get_token()
};
let claimed = (*claim_check.airdrop).is_claimed(*claim_check.claim_id);
let funded = token
.balanceOf(
*claim_check.airdrop.contract_address
) >= ((*claim_check.amount).into());
result.append(CheckResult { claimed, funded });
while let Option::Some(claim_check) = claims.pop_front() {
let token = IERC20Dispatcher {
contract_address: (*claim_check.airdrop).get_token()
};
let claimed = (*claim_check.airdrop).is_claimed(*claim_check.claim_id);
let funded = token
.balanceOf(
*claim_check.airdrop.contract_address
) >= ((*claim_check.amount).into());
result.append(CheckResult { claimed, funded });
};

result.span()
}
Expand Down
25 changes: 9 additions & 16 deletions src/airdrop_test.cairo
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use core::hash::{LegacyHash};
use core::num::traits::zero::{Zero};
use governance::airdrop::{
IAirdropDispatcher, IAirdropDispatcherTrait, Airdrop, Config,
Airdrop::{compute_pedersen_root, hash_function, hash_claim, compute_root_of_group}, Claim
};
use governance::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use governance::interfaces::erc20::{IERC20DispatcherTrait};
use governance::test::test_token::{TestToken, deploy as deploy_token};
use starknet::testing::{pop_log, set_block_timestamp};
use starknet::{
ClassHash, ContractAddress, contract_address_const, get_contract_address,
syscalls::{deploy_syscall}
ContractAddress, contract_address_const, get_contract_address, syscalls::{deploy_syscall}
};

fn deploy_with_refundee(
Expand Down Expand Up @@ -802,16 +800,12 @@ fn test_claim_128_double_claim() {

assert_eq!(airdrop.claim_128(claims.span().slice(0, 128), array![s2, rr].span()), 128);
let mut i: u64 = 0;
while let Option::Some(claimed) =
pop_log::<
Airdrop::Claimed
>(airdrop.contract_address) {
assert_eq!(
claimed.claim,
Claim { id: i, amount: 3, claimee: contract_address_const::<0xcdee>() }
);
i += 1;
};
while let Option::Some(claimed) = pop_log::<Airdrop::Claimed>(airdrop.contract_address) {
assert_eq!(
claimed.claim, Claim { id: i, amount: 3, claimee: contract_address_const::<0xcdee>() }
);
i += 1;
};

assert_eq!(airdrop.claim_128(claims.span().slice(0, 128), array![s2, rr].span()), 0);
assert_eq!(pop_log::<Airdrop::Claimed>(airdrop.contract_address).is_none(), true);
Expand All @@ -820,8 +814,7 @@ fn test_claim_128_double_claim() {
mod refundable {
use super::{
deploy_token, deploy_with_refundee, get_contract_address, contract_address_const,
set_block_timestamp, IAirdropDispatcherTrait, IERC20DispatcherTrait, TestToken,
ContractAddress
set_block_timestamp, IAirdropDispatcherTrait, IERC20DispatcherTrait, ContractAddress
};

fn refund_to() -> ContractAddress {
Expand Down
2 changes: 1 addition & 1 deletion src/call_trait.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core::hash::{HashStateTrait, HashStateExTrait, Hash};
use core::hash::{HashStateTrait, Hash};
use starknet::account::{Call};
use starknet::syscalls::{call_contract_syscall};

Expand Down
33 changes: 22 additions & 11 deletions src/governor.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use governance::execution_state::{ExecutionState};
use governance::staker::{IStakerDispatcher};
use starknet::account::{Call};
use starknet::{ContractAddress, storage_access::{StorePacking}, ClassHash};
use starknet::{ContractAddress, ClassHash};

#[derive(Copy, Drop, Serde, starknet::Store, PartialEq, Debug)]
pub struct ProposalInfo {
Expand All @@ -25,7 +25,8 @@ pub struct Config {
pub voting_start_delay: u64,
// The period during which votes are collected
pub voting_period: u64,
// Over how many seconds the voting weight is averaged for proposal voting as well as creation/cancellation
// Over how many seconds the voting weight is averaged for proposal voting as well as
// creation/cancellation
pub voting_weight_smoothing_duration: u64,
// How many total votes must be collected for the proposal
pub quorum: u128,
Expand All @@ -51,7 +52,8 @@ pub trait IGovernor<TContractState> {
// Execute the given proposal.
fn execute(ref self: TContractState, id: felt252, calls: Span<Call>) -> Span<Span<felt252>>;

// Attaches the given text to the proposal. Simply emits 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);

// Combines propose and describe methods.
Expand Down Expand Up @@ -83,7 +85,8 @@ pub trait IGovernor<TContractState> {
// - 1 means voted against
fn get_vote(self: @TContractState, id: felt252, voter: ContractAddress) -> u8;

// Changes the configuration of the governor. Only affects proposals created after the configuration change. Must be called by self, e.g. via a proposal.
// Changes the configuration of the governor. Only affects proposals created after the
// configuration change. Must be called by self, e.g. via a proposal.
fn reconfigure(ref self: TContractState, config: Config) -> u64;

// Replaces the code at this address. This must be self-called via a proposal.
Expand All @@ -100,6 +103,10 @@ pub mod Governor {
use core::poseidon::{PoseidonTrait};
use governance::call_trait::{HashSerializable, CallTrait};
use governance::staker::{IStakerDispatcherTrait};
use starknet::storage::{
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
StoragePointerWriteAccess
};
use starknet::storage_access::{Store, storage_base_address_from_felt252};
use starknet::{
get_block_timestamp, get_caller_address, get_contract_address,
Expand All @@ -110,6 +117,7 @@ pub mod Governor {
ClassHash
};


#[derive(starknet::Event, Drop)]
pub struct Proposed {
pub id: felt252,
Expand Down Expand Up @@ -163,12 +171,12 @@ pub mod Governor {
#[storage]
struct Storage {
staker: IStakerDispatcher,
config_versions: LegacyMap<u64, Config>,
config_versions: Map<u64, Config>,
latest_config_version: u64,
nonce: u64,
proposals: LegacyMap<felt252, ProposalInfo>,
latest_proposal_by_proposer: LegacyMap<ContractAddress, felt252>,
vote: LegacyMap<(felt252, ContractAddress), u8>,
proposals: Map<felt252, ProposalInfo>,
latest_proposal_by_proposer: Map<ContractAddress, felt252>,
vote: Map<(felt252, ContractAddress), u8>,
}

#[constructor]
Expand Down Expand Up @@ -216,7 +224,8 @@ pub mod Governor {
if latest_proposal_id.is_non_zero() {
let latest_proposal_state = self.get_proposal(latest_proposal_id).execution_state;

// if the proposal is not canceled, check that the voting for that proposal has ended
// if the proposal is not canceled, check that the voting for that proposal has
// ended
if latest_proposal_state.canceled.is_zero() {
assert(
latest_proposal_state.created
Expand Down Expand Up @@ -325,7 +334,8 @@ pub mod Governor {
assert(proposal.proposer == get_caller_address(), 'PROPOSER_ONLY');
assert(proposal.execution_state.canceled.is_zero(), 'ALREADY_CANCELED');

// this is prevented so that proposers cannot grief voters by creating proposals that they plan to cancel after the result is known
// this is prevented so that proposers cannot grief voters by creating proposals that
// they plan to cancel after the result is known
assert(
get_block_timestamp() < (proposal.execution_state.created
+ config.voting_start_delay),
Expand Down Expand Up @@ -473,7 +483,8 @@ pub mod Governor {
}
}

// This implementation exists solely for the purpose of allowing simulation of calls from the governor with the flag to skip validation
// This implementation exists solely for the purpose of allowing simulation of calls from the
// governor with the flag to skip validation
#[abi(embed_v0)]
impl GovernorAccountContractForSimulation of AccountContract<ContractState> {
fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
Expand Down
Loading

0 comments on commit 179ded8

Please sign in to comment.