Skip to content

Commit

Permalink
feat: proposition power proposal validation strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
pscott committed Aug 16, 2023
1 parent 7128eef commit 8a49b03
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ trait IProposalValidationStrategy<TContractState> {
self: @TContractState,
author: UserAddress,
params: Array<felt252>,
userParams: Array<felt252>
user_params: Array<felt252>
) -> bool;
}
1 change: 1 addition & 0 deletions starknet/src/proposal_validation_strategies.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod proposing_power;
mod vanilla;
85 changes: 85 additions & 0 deletions starknet/src/proposal_validation_strategies/proposing_power.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#[starknet::contract]
mod ProposingPowerProposalValidationStrategy {
use sx::interfaces::IProposalValidationStrategy;
use sx::types::{UserAddress, IndexedStrategy, IndexedStrategyTrait, Strategy};
use sx::interfaces::{IVotingStrategyDispatcher, IVotingStrategyDispatcherTrait};
use starknet::ContractAddress;
use starknet::info;
use traits::{Into, TryInto};
use option::OptionTrait;
use result::ResultTrait;
use array::{ArrayTrait, SpanTrait};
use serde::Serde;
use sx::utils::bits::BitSetter;
use box::BoxTrait;
use clone::Clone;

#[storage]
struct Storage {}

#[external(v0)]
impl ProposingPowerProposalValidationStrategy of IProposalValidationStrategy<ContractState> {
fn validate(
self: @ContractState,
author: UserAddress,
params: Array<felt252>, // [proposal_threshold: u256, allowed_strategies: Array<Strategy>]
user_params: Array<felt252> // [user_strategies: Array<IndexedStrategy>]
) -> bool {
let mut params_span = params.span();
let (proposal_threshold, allowed_strategies) = Serde::<(
u256, Array<Strategy>
)>::deserialize(ref params_span)
.unwrap();

let mut user_params_span = user_params.span();
let user_strategies = Serde::<Array<IndexedStrategy>>::deserialize(ref user_params_span)
.unwrap();

let timestamp: u32 = info::get_block_timestamp().try_into().unwrap();
let voting_power = self
.get_cumulative_power(author, timestamp, user_strategies, allowed_strategies);
voting_power >= proposal_threshold
}
}

#[generate_trait]
impl InternalImpl of InternalTrait {
fn get_cumulative_power(
self: @ContractState,
voter: UserAddress,
block_number: u32,
mut user_strategies: Array<IndexedStrategy>,
allowed_strategies: Array<Strategy>,
) -> u256 {
user_strategies.assert_no_duplicate_indices();
let mut total_voting_power = 0_u256;
loop {
match user_strategies.pop_front() {
Option::Some(indexed_strategy) => {
match allowed_strategies.get(indexed_strategy.index.into()) {
Option::Some(strategy) => {
let strategy: Strategy = strategy.unbox().clone();
total_voting_power += IVotingStrategyDispatcher {
contract_address: strategy.address
}
.get_voting_power(
block_number,
voter,
strategy.params,
indexed_strategy.params,
);
},
Option::None => {
panic_with_felt252('Invalid strategy index');
},
};
},
Option::None => {
break total_voting_power;
},
};
}
}
}
}

2 changes: 1 addition & 1 deletion starknet/src/proposal_validation_strategies/vanilla.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod VanillaProposalValidationStrategy {
self: @ContractState,
author: UserAddress,
params: Array<felt252>,
userParams: Array<felt252>
user_params: Array<felt252>
) -> bool {
true
}
Expand Down
2 changes: 2 additions & 0 deletions starknet/src/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ mod test_space;
mod test_upgrade;
mod test_stark_tx_auth;

mod proposal_validation_strategies;

mod mocks;
mod setup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod AlwaysFailProposalValidationStrategy {
self: @ContractState,
author: UserAddress,
params: Array<felt252>,
userParams: Array<felt252>
user_params: Array<felt252>
) -> bool {
false
}
Expand Down
1 change: 1 addition & 0 deletions starknet/src/tests/proposal_validation_strategies.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod proposing_power;
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#[cfg(test)]
mod tests {
use starknet::syscalls::deploy_syscall;
use traits::{TryInto};
use starknet::SyscallResult;
use array::{ArrayTrait, SpanTrait};
use result::ResultTrait;
use option::OptionTrait;
use sx::voting_strategies::vanilla::{VanillaVotingStrategy};
use sx::voting_strategies::merkle_whitelist::{MerkleWhitelistVotingStrategy};
use sx::utils::merkle::Leaf;
use sx::proposal_validation_strategies::proposing_power::{
ProposingPowerProposalValidationStrategy
};
use sx::interfaces::{
IProposalValidationStrategy, IProposalValidationStrategyDispatcher,
IProposalValidationStrategyDispatcherTrait
};
use sx::types::{IndexedStrategy, Strategy, UserAddress};
use serde::Serde;
use starknet::contract_address_const;
use clone::Clone;
use sx::tests::test_merkle_whitelist::merkle_utils::{
generate_merkle_data, generate_merkle_root, generate_proof
};

// #[test]
// #[available_gas(10000000000)]
fn test_vanilla_works() {
// deploy vanilla voting strategy
let (vanilla_contract, _) = deploy_syscall(
VanillaVotingStrategy::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false
)
.unwrap();

let vanilla_strategy = Strategy { address: vanilla_contract, params: array![], };

// create a proposal validation strategy
let (proposal_validation_contract, _) = deploy_syscall(
ProposingPowerProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array![].span(),
false
)
.unwrap();

let allowed_strategies = array![vanilla_strategy.clone()];
let proposal_threshold = 1_u256;
let mut params: Array<felt252> = array![];
proposal_threshold.serialize(ref params);
allowed_strategies.serialize(ref params);

// used strategies
let used_strategy = IndexedStrategy { index: 0, params: array![], };
let used_strategies = array![used_strategy.clone()];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let contract = IProposalValidationStrategyDispatcher {
contract_address: proposal_validation_contract,
};

let author = UserAddress::Starknet(contract_address_const::<0x123456789>());

// Vanilla should return 1 so it should be fine
let is_validated = contract.validate(author, params, user_params.clone());
assert(is_validated, 'not enough VP');

// Now increase threshold
let proposal_threshold = 2_u256;
let mut params: Array<felt252> = array![];
proposal_threshold.serialize(ref params);
allowed_strategies.serialize(ref params);

// Threshold is 2 but VP should be 1
let is_validated = contract.validate(author, params.clone(), user_params);
assert(!is_validated, 'Threshold should not be reached');

// But now if we add the vanilla voting strategy twice then it should be fine
let allowed_strategies = array![
vanilla_strategy.clone(), vanilla_strategy.clone()
]; // Add it twice
let proposal_threshold = 2_u256; // Threshold is still 2
let mut params: Array<felt252> = array![];
proposal_threshold.serialize(ref params);
allowed_strategies.serialize(ref params);

let used_strategy1 = used_strategy;
let used_strategy2 = IndexedStrategy { index: 1, params: array![], };
let used_strategies = array![used_strategy1, used_strategy2];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let is_validated = contract.validate(author, params, user_params);
assert(is_validated, 'should have 2 VP');
}

#[test]
#[available_gas(10000000000)]
fn test_merkle_whitelist_works() {
// deploy merkle whitelist contract
let (merkle_contract, _) = deploy_syscall(
MerkleWhitelistVotingStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array![].span(),
false
)
.unwrap();

// create proposal validation strategy based on the deployed merkle whitelist contract
let (proposal_validation_contract, _) = deploy_syscall(
ProposingPowerProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array![].span(),
false
)
.unwrap();

let contract = IProposalValidationStrategyDispatcher {
contract_address: proposal_validation_contract,
};

// Generate leaves
let voter1 = UserAddress::Starknet(contract_address_const::<0x111111>());
let voter2 = UserAddress::Starknet(contract_address_const::<0x111112>());
let voter3 = UserAddress::Starknet(contract_address_const::<0x111113>());
let leaf1 = Leaf { address: voter1, voting_power: 1 };
let leaf2 = Leaf { address: voter2, voting_power: 2 };
let leaf3 = Leaf { address: voter3, voting_power: 3 };

let members = array![leaf1, leaf2, leaf3];

let merkle_data = generate_merkle_data(members.span());

let proof1 = generate_proof(merkle_data.span(), 0);
let proof2 = generate_proof(merkle_data.span(), 1);
let proof3 = generate_proof(merkle_data.span(), 2);

let mut user_params = ArrayTrait::<felt252>::new();
leaf1.serialize(ref user_params);
proof1.serialize(ref user_params);

let root = generate_merkle_root(merkle_data.span());
let merkle_whitelist_strategy = Strategy {
address: merkle_contract, params: array![root],
};
let allowed_strategies = array![merkle_whitelist_strategy.clone()];
let proposal_threshold =
2_u256; // voter1 should not hit threshold but voter2 and voter3 should

let mut params: Array<felt252> = array![];
proposal_threshold.serialize(ref params);
allowed_strategies.serialize(ref params);

// setup for voter1
let author = leaf1.address;
let mut indexed_params = array![];
leaf1.serialize(ref indexed_params);
proof1.serialize(ref indexed_params);
let used_strategy = IndexedStrategy { index: 0, params: indexed_params, };
let used_strategies = array![used_strategy.clone()];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let is_validated = contract.validate(author, params.clone(), user_params.clone());
assert(!is_validated, 'should not have enough VP');

// setup for voter2
let author = leaf2.address;
let mut indexed_params = array![];
leaf2.serialize(ref indexed_params);
proof2.serialize(ref indexed_params);
let used_strategy = IndexedStrategy { index: 0, params: indexed_params, };
let used_strategies = array![used_strategy.clone()];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let is_validated = contract.validate(author, params.clone(), user_params.clone());
assert(is_validated, 'should have enough VP');

// setup for voter3
let author = leaf3.address;
let mut indexed_params = array![];
leaf3.serialize(ref indexed_params);
proof3.serialize(ref indexed_params);
let used_strategy = IndexedStrategy { index: 0, params: indexed_params, };
let used_strategies = array![used_strategy.clone()];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let is_validated = contract.validate(author, params.clone(), user_params.clone());
assert(is_validated, 'should have enough VP');

// -- Now let's mix merkle and vanilla voting strategies --

// deploy vanilla voting strategy
let (vanilla_contract, _) = deploy_syscall(
VanillaVotingStrategy::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false
)
.unwrap();

let vanilla_strategy = Strategy { address: vanilla_contract, params: array![], };

let allowed_strategies = array![
merkle_whitelist_strategy.clone(), vanilla_strategy.clone()
]; // update allowed strategies
let proposal_threshold = proposal_threshold; // threshold is left unchanged
let mut params: Array<felt252> = array![]; // update params
proposal_threshold.serialize(ref params);
allowed_strategies.serialize(ref params);

// voter 1 should now have enough voting power!
let author = leaf1.address;
let vanilla = IndexedStrategy { index: 1, params: array![], };
let mut indexed_params = array![];
leaf1.serialize(ref indexed_params);
proof1.serialize(ref indexed_params);
let merkle = IndexedStrategy { index: 0, params: indexed_params, };

let used_strategies = array![vanilla.clone(), merkle.clone()];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let is_validated = contract.validate(author, params.clone(), user_params.clone());
assert(is_validated, 'should have enough VP');

// and a random voter that doesn't use the whitelist should not have enough VP
let author = UserAddress::Starknet(contract_address_const::<0x123456789>());
let used_strategies = array![vanilla.clone()];
let mut user_params = array![];
used_strategies.serialize(ref user_params);

let is_validated = contract.validate(author, params.clone(), user_params.clone());
assert(!is_validated, 'should not have enough VP');
}
}
1 change: 0 additions & 1 deletion starknet/src/tests/test_upgrade.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ mod tests {
use option::OptionTrait;
use integer::u256_from_felt252;
use clone::Clone;
use debug::PrintTrait;
use serde::{Serde};

use sx::space::space::{Space, ISpaceDispatcher, ISpaceDispatcherTrait};
Expand Down
2 changes: 1 addition & 1 deletion starknet/src/types/indexed_strategy.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl IndexedStrategyImpl of IndexedStrategyTrait {
// Check that bit at index `strats[i].index` is not set.
let s = math::pow(u256 { low: 2_u128, high: 0_u128 }, *self.at(i).index);

assert((bit_map & s) == u256 { low: 1_u128, high: 0_u128 }, 'Duplicate Found');
assert((bit_map & s) != u256 { low: 1_u128, high: 0_u128 }, 'Duplicate Found');
// Update aforementioned bit.
bit_map = bit_map | s;
i += 1;
Expand Down
1 change: 0 additions & 1 deletion starknet/src/utils/merkle.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use serde::Serde;
use sx::types::UserAddress;
use clone::Clone;
use hash::{LegacyHash};
use debug::PrintTrait;
use sx::utils::legacy_hash::LegacyHashSpanFelt252;

/// Leaf struct for the merkle tree
Expand Down
Loading

0 comments on commit 8a49b03

Please sign in to comment.