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: whitelist proposal validation #476

Closed
wants to merge 5 commits into from
Closed
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;
}
2 changes: 2 additions & 0 deletions starknet/src/proposal_validation_strategies.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
mod merkle_whitelist;

mod vanilla;
44 changes: 44 additions & 0 deletions starknet/src/proposal_validation_strategies/merkle_whitelist.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#[starknet::contract]
mod MerkleWhitelistProposalValidationStrategy {
use sx::interfaces::IProposalValidationStrategy;
use serde::Serde;
use sx::types::UserAddress;
use array::{ArrayTrait, Span, SpanTrait};
use option::OptionTrait;
use sx::utils::merkle::{assert_valid_proof, Leaf};

const LEAF_SIZE: usize = 4; // Serde::<Leaf>::serialize().len()

#[storage]
struct Storage {}

#[derive(Drop, Serde)]
struct Input {
Copy link
Contributor

@Orland0x Orland0x Aug 16, 2023

Choose a reason for hiding this comment

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

could call something like StrategyParams, which we could use in all strategies to define the deserialization of params so its clear what this refers to.

root: felt252,
threshold: u256,
}

#[external(v0)]
impl MerkleWhitelistImpl of IProposalValidationStrategy<ContractState> {
fn validate(
self: @ContractState,
author: UserAddress,
params: Array<felt252>, // [root, threshold]
user_params: Array<felt252> // [Serde(leaf), Serde(proofs)]
) -> bool {
let cache = user_params.span(); // cache

let mut leaf_raw = cache.slice(0, LEAF_SIZE);
let leaf = Serde::<Leaf>::deserialize(ref leaf_raw).unwrap();

let mut proofs_raw = cache.slice(LEAF_SIZE, cache.len() - LEAF_SIZE);
let proofs = Serde::<Array<felt252>>::deserialize(ref proofs_raw).unwrap();

let mut sp4n = params.span();
let input = Serde::<Input>::deserialize(ref sp4n).unwrap();

assert_valid_proof(input.root, leaf, proofs.span());
leaf.voting_power >= input.threshold
}
}
}
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
4 changes: 3 additions & 1 deletion starknet/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod test_merkle_whitelist;
mod proposal_validation_strategies;
mod test_factory;
mod test_merkle;
mod test_space;
mod test_upgrade;
mod voting_strategies;

mod mocks;
mod setup;
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 test_merkle_whitelist;
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#[cfg(test)]
mod merkle_whitelist_proposal_strategy {
use array::{ArrayTrait, SpanTrait};
use sx::utils::merkle::Leaf;
use sx::tests::utils::merkle::{
generate_merkle_root, generate_n_members, generate_merkle_data, generate_proof
};
use sx::proposal_validation_strategies::merkle_whitelist::{
MerkleWhitelistProposalValidationStrategy
};
use sx::interfaces::{
IProposalValidationStrategy, IProposalValidationStrategyDispatcher,
IProposalValidationStrategyDispatcherTrait
};
use starknet::syscalls::deploy_syscall;
use starknet::SyscallResult;
use result::ResultTrait;
use option::OptionTrait;
use traits::TryInto;
use serde::Serde;
use starknet::contract_address_const;
use sx::types::UserAddress;

// Checks that that the leaf at the given index is correclty accepted or rejected (given a certain `threshold`).
Copy link
Contributor

Choose a reason for hiding this comment

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

correctly

fn check_index(
index: usize,
members: Span<Leaf>,
threshold: u256,
proposal_validation_strategy: IProposalValidationStrategyDispatcher
) {
let leaf = *members.at(index);
let proposer = leaf.address;

let merkle_data = generate_merkle_data(members);
let root = generate_merkle_root(merkle_data.span());
let proof = generate_proof(merkle_data.span(), index);

let mut params = ArrayTrait::<felt252>::new();
root.serialize(ref params);
threshold.serialize(ref params);

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

let is_valid = proposal_validation_strategy.validate(proposer, params, user_params);
if (leaf.voting_power >= threshold) {
assert(is_valid, 'Proposer got rejected');
} else {
assert(!is_valid, 'Proposer got accepted');
}
}

#[test]
#[available_gas(1000000000)]
fn just_enough_vp() {
let members = generate_n_members(20);
let threshold = 3_u256; // Voting power required to submit a proposal

let (contract, _) = deploy_syscall(
Copy link
Contributor

Choose a reason for hiding this comment

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

could put following 10 lines into a setup() utility as its duplicated in every test

MerkleWhitelistProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false,
)
.unwrap();
let proposal_validation_strategy = IProposalValidationStrategyDispatcher {
contract_address: contract
};

check_index(
2, members.span(), threshold, proposal_validation_strategy
); // Index 2 has voting power 3
}

#[test]
#[available_gas(1000000000)]
fn more_than_enough_vp() {
let members = generate_n_members(20);
let threshold = 3_u256; // Voting power required to submit a proposal

let (contract, _) = deploy_syscall(
MerkleWhitelistProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false,
)
.unwrap();
let proposal_validation_strategy = IProposalValidationStrategyDispatcher {
contract_address: contract
};

check_index(
3, members.span(), threshold, proposal_validation_strategy
); // Index 3 has voting power 4
}

#[test]
#[available_gas(1000000000)]
fn not_enough_vp() {
let members = generate_n_members(20);
let threshold = 3_u256; // Voting power required to submit a proposal

let (contract, _) = deploy_syscall(
MerkleWhitelistProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false,
)
.unwrap();
let proposal_validation_strategy = IProposalValidationStrategyDispatcher {
contract_address: contract
};

check_index(
1, members.span(), threshold, proposal_validation_strategy
); // Index 1 has voting power 2
}

#[test]
#[available_gas(1000000000)]
#[should_panic(expected: ('Merkle: Invalid proof', 'ENTRYPOINT_FAILED'))]
fn lying_voting_power() {
let members = generate_n_members(20);
let threshold = 0_u256;

let (contract, _) = deploy_syscall(
MerkleWhitelistProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false,
)
.unwrap();
let proposal_validation_strategy = IProposalValidationStrategyDispatcher {
contract_address: contract
};
let timestamp = 0x1234;
let index = 2;
let leaf = *members.at(index);
let voter = leaf.address;

let merkle_data = generate_merkle_data(members.span());
let root = generate_merkle_root(merkle_data.span());
let proof = generate_proof(merkle_data.span(), index);

let mut params = ArrayTrait::<felt252>::new();
root.serialize(ref params);
threshold.serialize(ref params);

let mut user_params = ArrayTrait::<felt252>::new();
let fake_leaf = Leaf {
address: leaf.address, voting_power: leaf.voting_power + 1,
}; // lying about voting power here
fake_leaf.serialize(ref user_params);
proof.serialize(ref user_params);

proposal_validation_strategy.validate(voter, params, user_params);
}

#[test]
#[available_gas(1000000000)]
#[should_panic(expected: ('Merkle: Invalid proof', 'ENTRYPOINT_FAILED'))]
fn lying_address() {
let members = generate_n_members(20);
let threshold = 0_u256;

let (contract, _) = deploy_syscall(
MerkleWhitelistProposalValidationStrategy::TEST_CLASS_HASH.try_into().unwrap(),
0,
array::ArrayTrait::<felt252>::new().span(),
false,
)
.unwrap();
let proposal_validation_strategy = IProposalValidationStrategyDispatcher {
contract_address: contract
};
let timestamp = 0x1234;
let index = 2;
let leaf = *members.at(index);
let proposer = leaf.address;

let merkle_data = generate_merkle_data(members.span());
let root = generate_merkle_root(merkle_data.span());
let proof = generate_proof(merkle_data.span(), index);

let mut params = ArrayTrait::<felt252>::new();
root.serialize(ref params);
threshold.serialize(ref params);

let mut user_params = ArrayTrait::<felt252>::new();
let fake_leaf = Leaf {
address: UserAddress::Starknet(contract_address_const::<0x1337>()),
voting_power: leaf.voting_power + 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like the voting power and address are both modified

}; // lying about voting power here
fake_leaf.serialize(ref user_params);
proof.serialize(ref user_params);

proposal_validation_strategy.validate(proposer, params, user_params);
}
}
Loading