diff --git a/starknet/src/authenticators.cairo b/starknet/src/authenticators.cairo index 3b2fa347..23a89502 100644 --- a/starknet/src/authenticators.cairo +++ b/starknet/src/authenticators.cairo @@ -3,3 +3,5 @@ mod vanilla; mod eth_tx; mod eth_sig; + +mod stark_tx; diff --git a/starknet/src/authenticators/stark_tx.cairo b/starknet/src/authenticators/stark_tx.cairo new file mode 100644 index 00000000..3f8f88a8 --- /dev/null +++ b/starknet/src/authenticators/stark_tx.cairo @@ -0,0 +1,108 @@ +use starknet::{ContractAddress}; +use sx::types::{Strategy, IndexedStrategy, Choice}; + +#[starknet::interface] +trait IStarkTxAuthenticator { + fn authenticate_propose( + ref self: TContractState, + space: ContractAddress, + author: ContractAddress, + execution_strategy: Strategy, + user_proposal_validation_params: Array, + metadata_URI: Array + ); + fn authenticate_vote( + ref self: TContractState, + space: ContractAddress, + voter: ContractAddress, + proposal_id: u256, + choice: Choice, + user_voting_strategies: Array, + metadata_URI: Array + ); + fn authenticate_update_proposal( + ref self: TContractState, + space: ContractAddress, + author: ContractAddress, + proposal_id: u256, + execution_strategy: Strategy, + metadata_URI: Array + ); +} + +#[starknet::contract] +mod StarkTxAuthenticator { + use super::IStarkTxAuthenticator; + use starknet::{ContractAddress, info}; + use core::array::ArrayTrait; + use sx::space::space::{ISpaceDispatcher, ISpaceDispatcherTrait}; + use sx::types::{UserAddress, Strategy, IndexedStrategy, Choice}; + + #[storage] + struct Storage {} + + #[external(v0)] + impl StarkTxAuthenticator of IStarkTxAuthenticator { + fn authenticate_propose( + ref self: ContractState, + space: ContractAddress, + author: ContractAddress, + execution_strategy: Strategy, + user_proposal_validation_params: Array, + metadata_URI: Array + ) { + assert(info::get_caller_address() == author, 'Invalid Caller'); + + ISpaceDispatcher { + contract_address: space + } + .propose( + UserAddress::Starknet(author), + execution_strategy, + user_proposal_validation_params, + metadata_URI + ); + } + + fn authenticate_vote( + ref self: ContractState, + space: ContractAddress, + voter: ContractAddress, + proposal_id: u256, + choice: Choice, + user_voting_strategies: Array, + metadata_URI: Array + ) { + assert(info::get_caller_address() == voter, 'Invalid Caller'); + + ISpaceDispatcher { + contract_address: space + } + .vote( + UserAddress::Starknet(voter), + proposal_id, + choice, + user_voting_strategies, + metadata_URI + ); + } + + fn authenticate_update_proposal( + ref self: ContractState, + space: ContractAddress, + author: ContractAddress, + proposal_id: u256, + execution_strategy: Strategy, + metadata_URI: Array + ) { + assert(info::get_caller_address() == author, 'Invalid Caller'); + + ISpaceDispatcher { + contract_address: space + } + .update_proposal( + UserAddress::Starknet(author), proposal_id, execution_strategy, metadata_URI + ); + } + } +} diff --git a/starknet/src/tests.cairo b/starknet/src/tests.cairo index 996a672c..f8471302 100644 --- a/starknet/src/tests.cairo +++ b/starknet/src/tests.cairo @@ -2,6 +2,7 @@ mod test_merkle_whitelist; mod test_factory; mod test_space; mod test_upgrade; +mod test_stark_tx_auth; mod mocks; mod setup; diff --git a/starknet/src/tests/test_stark_tx_auth.cairo b/starknet/src/tests/test_stark_tx_auth.cairo new file mode 100644 index 00000000..6ccdbe14 --- /dev/null +++ b/starknet/src/tests/test_stark_tx_auth.cairo @@ -0,0 +1,313 @@ +#[cfg(test)] +mod tests { + use array::ArrayTrait; + use starknet::{ + ContractAddress, syscalls::deploy_syscall, testing, contract_address_const, info + }; + use traits::{Into, TryInto}; + use result::ResultTrait; + use option::OptionTrait; + use integer::u256_from_felt252; + use clone::Clone; + use serde::{Serde}; + use sx::space::space::{Space, ISpaceDispatcher, ISpaceDispatcherTrait}; + use sx::authenticators::stark_tx::{ + StarkTxAuthenticator, IStarkTxAuthenticatorDispatcher, IStarkTxAuthenticatorDispatcherTrait + }; + use sx::execution_strategies::vanilla::VanillaExecutionStrategy; + use sx::voting_strategies::vanilla::VanillaVotingStrategy; + use sx::proposal_validation_strategies::vanilla::VanillaProposalValidationStrategy; + use sx::tests::setup::setup::setup::{setup, deploy}; + use sx::types::{ + UserAddress, Strategy, IndexedStrategy, Choice, FinalizationStatus, Proposal, + UpdateSettingsCalldataImpl, UpdateSettingsCalldata + }; + use sx::tests::utils::strategy_trait::{StrategyImpl}; + + fn setup_stark_tx_auth( + space: ISpaceDispatcher, owner: ContractAddress + ) -> IStarkTxAuthenticatorDispatcher { + // Deploy Stark Tx Authenticator and whitelist in Space + let (stark_tx_authenticator_address, _) = deploy_syscall( + StarkTxAuthenticator::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap(); + + let authenticator = IStarkTxAuthenticatorDispatcher { + contract_address: stark_tx_authenticator_address, + }; + let mut updateSettingsCalldata: UpdateSettingsCalldata = + UpdateSettingsCalldataImpl::default(); + updateSettingsCalldata.authenticators_to_add = array![stark_tx_authenticator_address]; + testing::set_contract_address(owner); + space.update_settings(updateSettingsCalldata); + + authenticator + } + + #[test] + #[available_gas(10000000000)] + fn test_propose_update_vote() { + let config = setup(); + let (factory, space) = deploy(@config); + let authenticator = setup_stark_tx_auth(space, config.owner); + + let quorum = u256_from_felt252(1); + let mut constructor_calldata = ArrayTrait::::new(); + quorum.serialize(ref constructor_calldata); + + let (vanilla_execution_strategy_address, _) = deploy_syscall( + VanillaExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(), + 0, + constructor_calldata.span(), + false + ) + .unwrap(); + let vanilla_execution_strategy = StrategyImpl::from_address( + vanilla_execution_strategy_address + ); + let author = contract_address_const::<0x5678>(); + + // Create Proposal + testing::set_contract_address(author); + authenticator + .authenticate_propose( + space.contract_address, + author, + vanilla_execution_strategy, + ArrayTrait::::new(), + ArrayTrait::::new() + ); + + assert( + space.next_proposal_id() == u256 { low: 2_u128, high: 0_u128 }, + 'next_proposal_id should be 2' + ); + + // Update Proposal + + let proposal_id = u256_from_felt252(1); + // Keeping the same execution strategy contract but changing the payload + let mut new_payload = ArrayTrait::::new(); + new_payload.append(1); + let new_execution_strategy = Strategy { + address: vanilla_execution_strategy_address, params: new_payload.clone() + }; + + testing::set_contract_address(author); + authenticator + .authenticate_update_proposal( + space.contract_address, + author, + proposal_id, + new_execution_strategy, + ArrayTrait::::new() + ); + + // Increasing block block_number by 1 to pass voting delay + testing::set_block_number(1_u64); + + let voter = contract_address_const::<0x8765>(); + let choice = Choice::For(()); + let mut user_voting_strategies = ArrayTrait::::new(); + user_voting_strategies + .append(IndexedStrategy { index: 0_u8, params: ArrayTrait::::new() }); + + // Vote on Proposal + testing::set_contract_address(voter); + authenticator + .authenticate_vote( + space.contract_address, + voter, + proposal_id, + choice, + user_voting_strategies, + ArrayTrait::::new() + ); + + testing::set_block_number(2_u64); + + // Execute Proposal + space.execute(u256_from_felt252(1), new_payload); + } + + #[test] + #[available_gas(10000000000)] + #[should_panic(expected: ('Invalid Caller', 'ENTRYPOINT_FAILED'))] + fn test_propose_invalid_caller() { + let config = setup(); + let (factory, space) = deploy(@config); + let authenticator = setup_stark_tx_auth(space, config.owner); + + let quorum = u256_from_felt252(1); + let mut constructor_calldata = ArrayTrait::::new(); + quorum.serialize(ref constructor_calldata); + + let (vanilla_execution_strategy_address, _) = deploy_syscall( + VanillaExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(), + 0, + constructor_calldata.span(), + false + ) + .unwrap(); + let vanilla_execution_strategy = StrategyImpl::from_address( + vanilla_execution_strategy_address + ); + let author = contract_address_const::<0x5678>(); + + // Create Proposal not from author account + testing::set_contract_address(config.owner); + authenticator + .authenticate_propose( + space.contract_address, + author, + vanilla_execution_strategy, + ArrayTrait::::new(), + ArrayTrait::::new() + ); + } + + #[test] + #[available_gas(10000000000)] + #[should_panic(expected: ('Invalid Caller', 'ENTRYPOINT_FAILED'))] + fn test_update_proposal_invalid_caller() { + let config = setup(); + let (factory, space) = deploy(@config); + let authenticator = setup_stark_tx_auth(space, config.owner); + + let quorum = u256_from_felt252(1); + let mut constructor_calldata = ArrayTrait::::new(); + quorum.serialize(ref constructor_calldata); + + let (vanilla_execution_strategy_address, _) = deploy_syscall( + VanillaExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(), + 0, + constructor_calldata.span(), + false + ) + .unwrap(); + let vanilla_execution_strategy = StrategyImpl::from_address( + vanilla_execution_strategy_address + ); + let author = contract_address_const::<0x5678>(); + + // Create Proposal + testing::set_contract_address(author); + authenticator + .authenticate_propose( + space.contract_address, + author, + vanilla_execution_strategy, + ArrayTrait::::new(), + ArrayTrait::::new() + ); + + assert( + space.next_proposal_id() == u256 { low: 2_u128, high: 0_u128 }, + 'next_proposal_id should be 2' + ); + + // Update Proposal + + let proposal_id = u256_from_felt252(1); + // Keeping the same execution strategy contract but changing the payload + let mut new_payload = ArrayTrait::::new(); + new_payload.append(1); + let new_execution_strategy = Strategy { + address: vanilla_execution_strategy_address, params: new_payload.clone() + }; + + // Update proposal not from author account + testing::set_contract_address(config.owner); + authenticator + .authenticate_update_proposal( + space.contract_address, + author, + proposal_id, + new_execution_strategy, + ArrayTrait::::new() + ); + } + + #[test] + #[available_gas(10000000000)] + #[should_panic(expected: ('Invalid Caller', 'ENTRYPOINT_FAILED'))] + fn test_vote_invalid_caller() { + let config = setup(); + let (factory, space) = deploy(@config); + let authenticator = setup_stark_tx_auth(space, config.owner); + + let quorum = u256_from_felt252(1); + let mut constructor_calldata = ArrayTrait::::new(); + quorum.serialize(ref constructor_calldata); + + let (vanilla_execution_strategy_address, _) = deploy_syscall( + VanillaExecutionStrategy::TEST_CLASS_HASH.try_into().unwrap(), + 0, + constructor_calldata.span(), + false + ) + .unwrap(); + let vanilla_execution_strategy = StrategyImpl::from_address( + vanilla_execution_strategy_address + ); + let author = contract_address_const::<0x5678>(); + + // Create Proposal + testing::set_contract_address(author); + authenticator + .authenticate_propose( + space.contract_address, + author, + vanilla_execution_strategy, + ArrayTrait::::new(), + ArrayTrait::::new() + ); + + assert( + space.next_proposal_id() == u256 { low: 2_u128, high: 0_u128 }, + 'next_proposal_id should be 2' + ); + + // Update Proposal + + let proposal_id = u256_from_felt252(1); + // Keeping the same execution strategy contract but changing the payload + let mut new_payload = ArrayTrait::::new(); + new_payload.append(1); + let new_execution_strategy = Strategy { + address: vanilla_execution_strategy_address, params: new_payload.clone() + }; + + testing::set_contract_address(author); + authenticator + .authenticate_update_proposal( + space.contract_address, + author, + proposal_id, + new_execution_strategy, + ArrayTrait::::new() + ); + + // Increasing block block_number by 1 to pass voting delay + testing::set_block_number(1_u64); + + let voter = contract_address_const::<0x8765>(); + let choice = Choice::For(()); + let mut user_voting_strategies = ArrayTrait::::new(); + user_voting_strategies + .append(IndexedStrategy { index: 0_u8, params: ArrayTrait::::new() }); + + // Vote on Proposal not from voter account + testing::set_contract_address(config.owner); + authenticator + .authenticate_vote( + space.contract_address, + voter, + proposal_id, + choice, + user_voting_strategies, + ArrayTrait::::new() + ); + } +}