Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: proposition power proposal validation strategy #496

Merged
merged 4 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
33 changes: 33 additions & 0 deletions starknet/src/proposal_validation_strategies/proposing_power.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#[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;
use sx::utils::proposition_power::_validate;

#[storage]
struct Storage {}

#[external(v0)]
impl ProposingPowerProposalValidationStrategy of IProposalValidationStrategy<ContractState> {
fn validate(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably makes sense to follow the same approach as sx-evm where we have the internal _validate function defined in utils then the strategy can call decode and call whichever ones it wants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self: @ContractState,
author: UserAddress,
params: Array<felt252>, // [proposal_threshold: u256, allowed_strategies: Array<Strategy>]
user_params: Array<felt252> // [user_strategies: Array<IndexedStrategy>]
) -> bool {
_validate(author, params, user_params)
}
}
}

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![];
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![];
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![];
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![];
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![]; // 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(2_u256, *self.at(i).index);

assert((bit_map & s) == 1_u256, 'Duplicate Found');
assert((bit_map & s) != u256 { low: 1_u128, high: 0_u128 }, 'Duplicate Found');
pscott marked this conversation as resolved.
Show resolved Hide resolved
// Update aforementioned bit.
bit_map = bit_map | s;
i += 1;
Expand Down
2 changes: 2 additions & 0 deletions starknet/src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ mod legacy_hash;
mod math;
mod merkle;

mod proposition_power;

mod struct_hash;

mod single_slot_proof;
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
Loading