-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: proposition power proposal validation strategy
- Loading branch information
Showing
12 changed files
with
331 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
mod proposing_power; | ||
mod vanilla; |
85 changes: 85 additions & 0 deletions
85
starknet/src/proposal_validation_strategies/proposing_power.cairo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}, | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mod proposing_power; |
236 changes: 236 additions & 0 deletions
236
starknet/src/tests/proposal_validation_strategies/proposing_power.cairo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.