From c5df72f84123e7699465237aeeddd8524d6fb5cf Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 17 Sep 2024 15:25:16 -0400 Subject: [PATCH] refactor tests --- .../mocks/mock_multisig_target.cairo | 23 +- contracts/src/mcms.cairo | 2 - .../src/tests/test_mcms/test_execute.cairo | 387 +++++++++++++--- .../src/tests/test_mcms/test_set_config.cairo | 183 +------- .../src/tests/test_mcms/test_set_root.cairo | 434 +++++------------- contracts/src/tests/test_mcms/utils.cairo | 402 ++++++++++++++-- 6 files changed, 836 insertions(+), 595 deletions(-) diff --git a/contracts/src/libraries/mocks/mock_multisig_target.cairo b/contracts/src/libraries/mocks/mock_multisig_target.cairo index ca6fd450f..2c0fd05ee 100644 --- a/contracts/src/libraries/mocks/mock_multisig_target.cairo +++ b/contracts/src/libraries/mocks/mock_multisig_target.cairo @@ -1,6 +1,17 @@ +use array::ArrayTrait; + +#[starknet::interface] +trait IMockMultisigTarget { + fn increment(ref self: TContractState, val1: felt252, val2: felt252) -> Array; + fn set_value(ref self: TContractState, value: felt252); + fn flip_toggle(ref self: TContractState); + fn read(self: @TContractState) -> (felt252, bool); +} + #[starknet::contract] mod MockMultisigTarget { use array::ArrayTrait; + use super::IMockMultisigTarget; #[storage] struct Storage { @@ -8,22 +19,22 @@ mod MockMultisigTarget { toggle: bool } - #[abi(per_item)] - #[generate_trait] - impl HelperImpl of HelperTrait { - #[external(v0)] + #[abi(embed_v0)] + impl MockMultisigTargetImpl of super::IMockMultisigTarget { fn increment(ref self: ContractState, val1: felt252, val2: felt252) -> Array { array![val1 + 1, val2 + 1] } - #[external(v0)] fn set_value(ref self: ContractState, value: felt252) { self.value.write(value); } - #[external(v0)] fn flip_toggle(ref self: ContractState) { self.toggle.write(!self.toggle.read()); } + + fn read(self: @ContractState) -> (felt252, bool) { + (self.value.read(), self.toggle.read()) + } } } diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 3d27da42c..c5c63d2b4 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -388,7 +388,6 @@ mod ManyChainMultiSig { root: root, valid_until: valid_until, op_count: metadata.pre_op_count } ); - // todo: add set root metadata self.s_root_metadata.write(metadata); self .emit( @@ -438,7 +437,6 @@ mod ManyChainMultiSig { new_expiring_root_and_op_count.op_count += 1; self.s_expiring_root_and_op_count.write(new_expiring_root_and_op_count); - // todo: execute self._execute(op.to, op.selector, op.data); self diff --git a/contracts/src/tests/test_mcms/test_execute.cairo b/contracts/src/tests/test_mcms/test_execute.cairo index 87fc1cc0c..053f8c149 100644 --- a/contracts/src/tests/test_mcms/test_execute.cairo +++ b/contracts/src/tests/test_mcms/test_execute.cairo @@ -1,60 +1,343 @@ +use starknet::{contract_address_const, EthAddress}; +use chainlink::libraries::mocks::mock_multisig_target::{ + IMockMultisigTarget, IMockMultisigTargetDispatcherTrait, IMockMultisigTargetDispatcher +}; use chainlink::mcms::{ - recover_eth_ecdsa, hash_pair, hash_op, hash_metadata, ExpiringRootAndOpCount, RootMetadata, - Config, Signer, eip_191_message_hash, ManyChainMultiSig, Op, - ManyChainMultiSig::{ - NewRoot, InternalFunctionsTrait, contract_state_for_testing, - s_signersContractMemberStateTrait, s_expiring_root_and_op_countContractMemberStateTrait, - s_root_metadataContractMemberStateTrait - }, + ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, Op, IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, - ManyChainMultiSig::{MAX_NUM_SIGNERS}, }; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, - stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_chain_id_global, - spy_events, EventSpyAssertionsTrait, // Add for assertions on the EventSpy - test_address, // the contract being tested, - start_cheat_chain_id, + declare, ContractClassTrait, start_cheat_chain_id_global, spy_events, + EventSpyAssertionsTrait, // Add for assertions on the EventSpy cheatcodes::{events::{EventSpy}}, start_cheat_block_timestamp_global, - start_cheat_block_timestamp, start_cheat_account_contract_address_global, - start_cheat_account_contract_address }; -use chainlink::tests::test_mcms::test_set_config::{setup_2_of_2_mcms_no_root, setup}; -use chainlink::tests::test_mcms::test_set_root::{new_setup_2_of_2_mcms}; -// 1. test no more operations to execute -// 2. test wrong chain id -// 3. test wrong multisig address -// 4. test root has expired -// 5. test wrong nonce -// 6. test wrong proof -// 7. test contract call fails (it doesn't exist) -// 8. test success - -// #[test] -// fn test_success() { -// let ( -// mut spy, -// mcms_address, -// mcms, -// safe_mcms, -// config, -// signer_addresses, -// signer_groups, -// group_quorums, -// group_parents, -// clear_root, -// root, -// valid_until, -// metadata, -// metadata_proof, -// signatures, -// ops -// ) = -// new_setup_2_of_2_mcms(); - -// mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); - -// // execute -// } +use chainlink::tests::test_mcms::utils::{setup_mcms_deploy_set_config_and_set_root}; + + +#[test] +fn test_success() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let op1 = *ops.at(0); + let op1_proof = *ops_proof.at(0); + + let target_address = op1.to; + let target = IMockMultisigTargetDispatcher { contract_address: target_address }; + + let (value, toggle) = target.read(); + assert(value == 0, 'should be 0'); + assert(toggle == false, 'should be false'); + + mcms.execute(op1, op1_proof); + + spy + .assert_emitted( + @array![ + ( + mcms_address, + ManyChainMultiSig::Event::OpExecuted( + ManyChainMultiSig::OpExecuted { + nonce: op1.nonce, to: op1.to, selector: op1.selector, data: op1.data + } + ) + ) + ] + ); + + assert(mcms.get_op_count() == 1, 'op count should be 1'); + + let (new_value, _) = target.read(); + assert(new_value == 1234123, 'value should be updated'); + + let op2 = *ops.at(1); + let op2_proof = *ops_proof.at(1); + + mcms.execute(op2, op2_proof); + + spy + .assert_emitted( + @array![ + ( + mcms_address, + ManyChainMultiSig::Event::OpExecuted( + ManyChainMultiSig::OpExecuted { + nonce: op2.nonce, to: op2.to, selector: op2.selector, data: op2.data + } + ) + ) + ] + ); + + assert(mcms.get_op_count() == 2, 'op count should be 2'); + + let (_, new_toggle) = target.read(); + assert(new_toggle == true, 'toggled should be true'); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_no_more_ops_to_execute() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let op1 = *ops.at(0); + let op1_proof = *ops_proof.at(0); + + let op2 = *ops.at(1); + let op2_proof = *ops_proof.at(1); + + mcms.execute(op1, op1_proof); + mcms.execute(op2, op2_proof); + + let result = safe_mcms.execute(op1, op1_proof); + match result { + Result::Ok(_) => panic!("expect 'post-operation count reached'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'post-operation count reached', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_wrong_chain_id() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let op1 = *ops.at(0); + let op1_proof = *ops_proof.at(0); + + start_cheat_chain_id_global(1231); + let result = safe_mcms.execute(op1, op1_proof); + + match result { + Result::Ok(_) => panic!("expect 'wrong chain id'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'wrong chain id', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_wrong_multisig_address() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let mut op1 = *ops.at(0); + op1.multisig = contract_address_const::<119922>(); + let op1_proof = *ops_proof.at(0); + + start_cheat_chain_id_global(1231); + let result = safe_mcms.execute(op1, op1_proof); + + match result { + Result::Ok(_) => panic!("expect 'wrong chain id'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'wrong chain id', *panic_data.at(0)); + } + } +} + + +#[test] +#[feature("safe_dispatcher")] +fn test_root_expired() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let op1 = *ops.at(0); + let op1_proof = *ops_proof.at(0); + + start_cheat_block_timestamp_global(valid_until.into() + 1); + let result = safe_mcms.execute(op1, op1_proof); + + match result { + Result::Ok(_) => panic!("expect 'root has expired'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'root has expired', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_wrong_nonce() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let mut op1 = *ops.at(0); + op1.nonce = 100; + let op1_proof = *ops_proof.at(0); + + let result = safe_mcms.execute(op1, op1_proof); + + match result { + Result::Ok(_) => panic!("expect 'wrong nonce'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'wrong nonce', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_proof_verification_failed() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + let op1 = *ops.at(0); + let bad_proof = array![0x12312312312321]; + + let result = safe_mcms.execute(op1, bad_proof.span()); + + match result { + Result::Ok(_) => panic!("expect 'proof verification failed'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'proof verification failed', *panic_data.at(0)); + } + } +} diff --git a/contracts/src/tests/test_mcms/test_set_config.cairo b/contracts/src/tests/test_mcms/test_set_config.cairo index 7a21eca57..79db2d323 100644 --- a/contracts/src/tests/test_mcms/test_set_config.cairo +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -14,7 +14,6 @@ use chainlink::mcms::{ IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, ManyChainMultiSig::{MAX_NUM_SIGNERS}, }; - use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, stop_cheat_caller_address, stop_cheat_caller_address_global, spy_events, @@ -23,43 +22,14 @@ use snforge_std::{ start_cheat_chain_id, cheatcodes::{events::{EventSpy}} }; - -// set_config tests - -// 1. test if lena(signer_address) = 0 => revert -// 2. test if lena(signer_address) > MAX_NUM_SIGNERS => revert - -// 3. test if signer addresses and signer groups not same size - -// 4. test if group_quorum and group_parents not same size - -// 6. test if one of signer_group #'s is out of bounds NUM_GROUPS - -// 7. test if group_parents[i] is greater than or equal to i (when not 0) there is revert -// 8. test if i is 0 and group_parents[i] != 0 and revert - -// 9. test if there is a signer in a group where group_quorum[i] == 0 => revert -// 10. test if there are not enough signers to meet a quorum => revert - -// 11. test if signer addresses are not in ascending order -// 12. successful => test without clearing root. test the state of storage variables and that event was emitted - -fn setup() -> (ContractAddress, IManyChainMultiSigDispatcher, IManyChainMultiSigSafeDispatcher) { - let calldata = array![]; - - let (mcms_address, _) = declare("ManyChainMultiSig").unwrap().deploy(@calldata).unwrap(); - - ( - mcms_address, - IManyChainMultiSigDispatcher { contract_address: mcms_address }, - IManyChainMultiSigSafeDispatcher { contract_address: mcms_address } - ) -} +use chainlink::tests::test_mcms::utils::{ + setup_mcms_deploy, setup_mcms_deploy_and_set_config_2_of_2 +}; #[test] #[feature("safe_dispatcher")] fn test_not_owner() { - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![]; let signer_groups = array![]; @@ -91,7 +61,7 @@ fn test_not_owner() { #[feature("safe_dispatcher")] fn test_set_config_out_of_bound_signers() { // 1. test if len(signer_address) = 0 => revert - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![]; let signer_groups = array![]; @@ -148,7 +118,7 @@ fn test_set_config_out_of_bound_signers() { #[feature("safe_dispatcher")] fn test_set_config_signer_groups_len_mismatch() { // 3. test if signer addresses and signer groups not same size - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![]; @@ -177,7 +147,7 @@ fn test_set_config_signer_groups_len_mismatch() { #[feature("safe_dispatcher")] fn test_set_config_group_quorums_parents_mismatch() { // 4. test if group_quorum and group_parents not length 32 - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; @@ -232,7 +202,7 @@ fn test_set_config_group_quorums_parents_mismatch() { #[feature("safe_dispatcher")] fn test_set_config_signers_group_out_of_bounds() { // 6. test if one of signer_group #'s is out of bounds NUM_GROUPS - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![33]; @@ -274,7 +244,7 @@ fn test_set_config_signers_group_out_of_bounds() { #[feature("safe_dispatcher")] fn test_set_config_group_tree_malformed() { // 7. test if group_parents[i] is greater than or equal to i (when not 0) there is revert - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; @@ -423,7 +393,7 @@ fn test_set_config_group_tree_malformed() { #[feature("safe_dispatcher")] fn test_set_config_signer_in_disabled_group() { // 9. test if there is a signer in a group where group_quorum[i] == 0 => revert - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let mut signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; @@ -518,7 +488,7 @@ fn test_set_config_signer_in_disabled_group() { #[test] #[feature("safe_dispatcher")] fn test_set_config_quorum_impossible() { - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let mut signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; @@ -613,7 +583,7 @@ fn test_set_config_quorum_impossible() { #[test] #[feature("safe_dispatcher")] fn test_set_config_signer_addresses_not_sorted() { - let (_, _, mcms_safe) = setup(); + let (_, _, mcms_safe) = setup_mcms_deploy(); let mut signer_addresses: Array = array![ // 0x1 address @@ -707,121 +677,6 @@ fn test_set_config_signer_addresses_not_sorted() { } } -fn setup_2_of_2_mcms_no_root( - signer_address_1: EthAddress, signer_address_2: EthAddress -) -> ( - EventSpy, - ContractAddress, - IManyChainMultiSigDispatcher, - IManyChainMultiSigSafeDispatcher, - Config, - Array, - Array, - Array, - Array, - bool -) { - let (mcms_address, mcms, safe_mcms) = setup(); - - let signer_addresses: Array = array![signer_address_1, signer_address_2]; - let signer_groups = array![0, 0]; - let group_quorums = array![ - 2, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ]; - let group_parents = array![ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ]; - let clear_root = false; - - let mut spy = spy_events(); - - mcms - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - let config = mcms.get_config(); - - ( - spy, - mcms_address, - mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root - ) -} - // test success, root not cleared, event emitted // 12. successful => test without clearing root. test the state of storage variables and that event was emitted // @@ -849,7 +704,7 @@ fn test_set_config_success_dont_clear_root() { group_parents, clear_root ) = - setup_2_of_2_mcms_no_root( + setup_mcms_deploy_and_set_config_2_of_2( signer_address_1, signer_address_2 ); @@ -897,12 +752,6 @@ fn test_set_config_success_dont_clear_root() { let signer_1 = state.get_signer_by_address(signer_address_1); let signer_2 = state.get_signer_by_address(signer_address_2); - // println!("expected signer 1 {:?}", expected_signer_1); - // println!("signer 1 {:?}", signer_1); - - // println!("expected signer 2 {:?}", expected_signer_2); - // println!("signer 2 {:?}", signer_2); - assert(signer_1 == expected_signer_1, 'signer 1 not equal'); assert(signer_2 == expected_signer_2, 'signer 2 not equal'); @@ -945,12 +794,6 @@ fn test_set_config_success_dont_clear_root() { let new_signer_1 = state.get_signer_by_address(new_signer_address_1); let new_signer_2 = state.get_signer_by_address(new_signer_address_2); - // println!("new expected signer 1 {:?}", new_expected_signer_1); - // println!("new signer 1 {:?}", new_signer_1); - - // println!("new expected signer 2 {:?}", new_expected_signer_2); - // println!("new signer 2 {:?}", new_signer_2); - assert(new_signer_1 == new_expected_signer_1, 'new signer 1 not equal'); assert(new_signer_2 == new_expected_signer_2, 'new signer 2 not equal'); diff --git a/contracts/src/tests/test_mcms/test_set_root.cairo b/contracts/src/tests/test_mcms/test_set_root.cairo index 84594c91e..e22058412 100644 --- a/contracts/src/tests/test_mcms/test_set_root.cairo +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -24,8 +24,10 @@ use chainlink::mcms::{ IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, ManyChainMultiSig::{MAX_NUM_SIGNERS}, }; -use chainlink::tests::test_mcms::test_set_config::{setup_2_of_2_mcms_no_root, setup}; -use chainlink::tests::test_mcms::utils::{insecure_sign}; +use chainlink::tests::test_mcms::utils::{ + insecure_sign, setup_signers, SignerMetadata, setup_mcms_deploy_and_set_config_2_of_2, + setup_mcms_deploy_set_config_and_set_root, set_root_args +}; use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, @@ -38,22 +40,9 @@ use snforge_std::{ start_cheat_account_contract_address }; -// test to add root - -// 1. set up mcms contract -// 2. set up a dummy contract (like mock multisig target or a new contract) -// 3. propose Op struct (of 2 ) and metadata -// 4. generate a root -// 5. abi_encode(root and valid_until) -// 6. create message hash -// 7. sign the message hash (how to do?) -- i'll do it via typescript and just input the hash here -// leaves = [...txs.map(txCoder), ...metadata.map(metadataCoder)] (metadata is the last leaf) <-- sort pairs and sort leaves -// https://www.npmjs.com/package/merkletreejs -// the proof does not include the root or the leaf - // simplified logic will only work when len(ops) = 2 // metadata nodes is the last leaf so that len(leafs) = 3 -fn merkle_root(leafs: Array) -> (u256, Span, Span, Span) { +fn merkle_root(leafs: Array) -> (u256, Span, Span>) { let mut level: Array = ArrayTrait::new(); let metadata = *leafs.at(leafs.len() - 1); @@ -88,23 +77,16 @@ fn merkle_root(leafs: Array) -> (u256, Span, Span, Span) // based on merkletree.js lib we use, the odd leaf out is not hashed until the very end let root = hash_pair(*level.at(0), metadata); - (root, array![metadata_proof].span(), proof1.span(), proof2.span()) + (root, array![metadata_proof].span(), array![proof1.span(), proof2.span()].span()) } -#[derive(Copy, Drop, Serde)] -struct SignerMetadata { - address: EthAddress, - private_key: u256 -} - - fn generate_set_root_params_custom_op_count( mcms_address: ContractAddress, target_address: ContractAddress, mut signers_metadata: Array, pre_op_count: u64, post_op_count: u64 -) -> (u256, u32, RootMetadata, Span, Array, Array) { +) -> (u256, u32, RootMetadata, Span, Array, Array, Span>) { let mock_chain_id = 732; // first operation @@ -120,7 +102,7 @@ fn generate_set_root_params_custom_op_count( }; // second operation - let selector2 = selector!("toggle"); + let selector2 = selector!("flip_toggle"); let calldata2 = array![]; let op2 = Op { chain_id: mock_chain_id.into(), @@ -146,7 +128,7 @@ fn generate_set_root_params_custom_op_count( let metadata_hash = hash_metadata(metadata, valid_until); // create merkle tree - let (root, metadata_proof, _, _) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); + let (root, metadata_proof, ops_proof) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); let message_hash = eip_191_message_hash(encoded_root.keccak()); @@ -167,82 +149,11 @@ fn generate_set_root_params_custom_op_count( let ops = array![op1.clone(), op2.clone()]; - (root, valid_until, metadata, metadata_proof, signatures, ops) + (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) } - -fn generate_set_root_params_1( - mcms_address: ContractAddress, - target_address: ContractAddress, - mut signers_metadata: Array -) -> (u256, u32, RootMetadata, Span, Array, Array) { - let mock_chain_id = 732; - - // first operation - let selector1 = selector!("set_value"); - let calldata1: Array = array![1234123]; - let op1 = Op { - chain_id: mock_chain_id.into(), - multisig: mcms_address, - nonce: 0, - to: target_address, - selector: selector1, - data: calldata1.span() - }; - - // second operation - let selector2 = selector!("toggle"); - let calldata2 = array![]; - let op2 = Op { - chain_id: mock_chain_id.into(), - multisig: mcms_address, - nonce: 1, - to: target_address, - selector: selector2, - data: calldata2.span() - }; - - let metadata = RootMetadata { - chain_id: mock_chain_id.into(), - multisig: mcms_address, - pre_op_count: 0, - post_op_count: 2, - override_previous_root: false, - }; - let valid_until = 9; - - let op1_hash = hash_op(op1); - let op2_hash = hash_op(op2); - - let metadata_hash = hash_metadata(metadata, valid_until); - - // create merkle tree - let (root, metadata_proof, _, _) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); - - let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); - let message_hash = eip_191_message_hash(encoded_root.keccak()); - - let mut signatures: Array = ArrayTrait::new(); - - while let Option::Some(signer_metadata) = signers_metadata - .pop_front() { - let (r, s, y_parity) = insecure_sign(message_hash, signer_metadata.private_key); - let signature = Signature { r: r, s: s, y_parity: y_parity }; - let address = recover_eth_ecdsa(message_hash, signature).unwrap(); - - // sanity check - assert(address == signer_metadata.address, 'signer not equal'); - - signatures.append(signature); - }; - - let ops = array![op1.clone(), op2.clone()]; - - (root, valid_until, metadata, metadata_proof, signatures, ops) -} - -// sets up root -fn new_setup_2_of_2_mcms() -> ( +// sets up root but with wrong multisig address in metadata +fn setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG() -> ( EventSpy, ContractAddress, IManyChainMultiSigDispatcher, @@ -258,22 +169,11 @@ fn new_setup_2_of_2_mcms() -> ( RootMetadata, Span, Array, - Array + Array, + Span>, ) { - let signer_address_1: EthAddress = (0x13Cf92228941e27eBce80634Eba36F992eCB148A) - .try_into() - .unwrap(); - let private_key_1: u256 = 0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688; - - let signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) - .try_into() - .unwrap(); - let private_key_2: u256 = 0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527; - - let signer_metadata = array![ - SignerMetadata { address: signer_address_1, private_key: private_key_1 }, - SignerMetadata { address: signer_address_2, private_key: private_key_2 } - ]; + let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = + setup_signers(); let ( mut spy, @@ -287,86 +187,7 @@ fn new_setup_2_of_2_mcms() -> ( group_parents, clear_root ) = - setup_2_of_2_mcms_no_root( - signer_address_1, signer_address_2 - ); - - let calldata = ArrayTrait::new(); - let mock_target_contract = declare("MockMultisigTarget").unwrap(); - let (target_address, _) = mock_target_contract.deploy(@calldata).unwrap(); - - let (root, valid_until, metadata, metadata_proof, signatures, ops) = generate_set_root_params_1( - mcms_address, target_address, signer_metadata - ); - - // mock chain id & timestamp - start_cheat_chain_id_global(metadata.chain_id.try_into().unwrap()); - - let mock_timestamp = 3; - start_cheat_block_timestamp_global(mock_timestamp); - - ( - spy, - mcms_address, - mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, - root, - valid_until, - metadata, - metadata_proof, - signatures, - ops - ) -} - -// sets up root -fn new_setup_2_of_2_mcms_wrong_multisig() -> ( - EventSpy, - ContractAddress, - IManyChainMultiSigDispatcher, - IManyChainMultiSigSafeDispatcher, - Config, - Array, - Array, - Array, - Array, - bool, // clear root - u256, - u32, - RootMetadata, - Span, - Array, - Array -) { - let signer_address_1: EthAddress = (0x13Cf92228941e27eBce80634Eba36F992eCB148A) - .try_into() - .unwrap(); - let private_key_1: u256 = 0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688; - - let signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) - .try_into() - .unwrap(); - let private_key_2: u256 = 0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527; - - let ( - mut spy, - mcms_address, - mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root - ) = - setup_2_of_2_mcms_no_root( + setup_mcms_deploy_and_set_config_2_of_2( signer_address_1, signer_address_2 ); @@ -394,7 +215,7 @@ fn new_setup_2_of_2_mcms_wrong_multisig() -> ( // second operation // todo update - let selector2 = selector!("toggle"); + let selector2 = selector!("flip_toggle"); let calldata2 = array![]; let op2 = Op { chain_id: mock_chain_id.into(), @@ -420,7 +241,7 @@ fn new_setup_2_of_2_mcms_wrong_multisig() -> ( let metadata_hash = hash_metadata(metadata, valid_until); // create merkle tree - let (root, metadata_proof, _, _) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); + let (root, metadata_proof, ops_proof) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); let message_hash = eip_191_message_hash(encoded_root.keccak()); @@ -457,7 +278,8 @@ fn new_setup_2_of_2_mcms_wrong_multisig() -> ( metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) } @@ -479,9 +301,10 @@ fn test_set_root_success() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); @@ -526,9 +349,10 @@ fn test_set_root_hash_seen() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); mcms.set_root(root, valid_until, metadata, metadata_proof, signatures.clone()); @@ -561,9 +385,10 @@ fn test_set_root_signatures_wrong_order() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); let unsorted_signatures = array![*signatures.at(1), *signatures.at(0)]; @@ -597,9 +422,10 @@ fn test_set_root_signatures_invalid_signer() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); let invalid_signatures = array![ signature_from_vrs( @@ -652,9 +478,10 @@ fn test_insufficient_signers() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); let missing_1_signature = array![*signatures.at(0)]; @@ -688,9 +515,10 @@ fn test_valid_until_expired() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); // cheat block timestamp start_cheat_block_timestamp_global(valid_until.into() + 1); @@ -724,9 +552,10 @@ fn test_invalid_metadata_proof() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); let invalid_metadata_proof = array![*metadata_proof.at(0), *metadata_proof.at(0)]; @@ -760,9 +589,10 @@ fn test_invalid_chain_id() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); start_cheat_chain_id_global(123123); @@ -795,9 +625,10 @@ fn test_invalid_multisig_address() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms_wrong_multisig(); + setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG(); let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); @@ -828,30 +659,19 @@ fn test_pending_ops_remain() { metadata, metadata_proof, signatures, - ops + ops, + ops_proof ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); // first time passes mcms.set_root(root, valid_until, metadata, metadata_proof, signatures.clone()); // sign a different set of operations with same signers - let signer_address_1: EthAddress = (0x13Cf92228941e27eBce80634Eba36F992eCB148A) - .try_into() - .unwrap(); - let private_key_1: u256 = 0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688; - - let signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) - .try_into() - .unwrap(); - let private_key_2: u256 = 0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527; - - let mut signer_metadata = array![ - SignerMetadata { address: signer_address_1, private_key: private_key_1 }, - SignerMetadata { address: signer_address_2, private_key: private_key_2 } - ]; - - let (root, valid_until, metadata, metadata_proof, signatures, ops) = generate_set_root_params_1( + let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = + setup_signers(); + let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = + set_root_args( mcms_address, contract_address_const::<123123>(), signer_metadata ); @@ -886,29 +706,16 @@ fn test_wrong_pre_op_count() { metadata, metadata_proof, signatures, - ops + ops, + _ ) = - new_setup_2_of_2_mcms(); + setup_mcms_deploy_set_config_and_set_root(); // sign a different set of operations with same signers - let signer_address_1: EthAddress = (0x13Cf92228941e27eBce80634Eba36F992eCB148A) - .try_into() - .unwrap(); - let private_key_1: u256 = 0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688; - - let signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) - .try_into() - .unwrap(); - let private_key_2: u256 = 0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527; - - let mut signer_metadata = array![ - SignerMetadata { address: signer_address_1, private_key: private_key_1 }, - SignerMetadata { address: signer_address_2, private_key: private_key_2 } - ]; - + let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = + setup_signers(); let wrong_pre_op_count = 1; - - let (root, valid_until, metadata, metadata_proof, signatures, ops) = + let (root, valid_until, metadata, metadata_proof, signatures, _, _) = generate_set_root_params_custom_op_count( mcms_address, contract_address_const::<123123>(), @@ -932,66 +739,63 @@ fn test_wrong_pre_op_count() { // todo: do two executes in between and then set the wrong root -// #[test] -// #[feature("safe_dispatcher")] -// fn test_wrong_post_op_count() { -// let ( -// mut spy, -// mcms_address, -// mcms, -// safe_mcms, -// config, -// signer_addresses, -// signer_groups, -// group_quorums, -// group_parents, -// clear_root, -// root, -// valid_until, -// metadata, -// metadata_proof, -// signatures, -// ops -// ) = -// new_setup_2_of_2_mcms(); - -// // sign a different set of operations with same signers -// let signer_address_1: EthAddress = (0x13Cf92228941e27eBce80634Eba36F992eCB148A) -// .try_into() -// .unwrap(); -// let private_key_1: u256 = 0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688; - -// let signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) -// .try_into() -// .unwrap(); -// let private_key_2: u256 = 0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527; - -// let mut signer_metadata = array![ -// SignerMetadata { address: signer_address_1, private_key: private_key_1 }, -// SignerMetadata { address: signer_address_2, private_key: private_key_2 } -// ]; - -// let wrong_pre_op_count = 1; - -// let (root, valid_until, metadata, metadata_proof, signatures, ops) = -// generate_set_root_params_custom_op_count( -// mcms_address, -// contract_address_const::<123123>(), -// signer_metadata, -// wrong_pre_op_count, -// wrong_pre_op_count + 2 -// ); - -// // first time passes -// let result = safe_mcms -// .set_root(root, valid_until, metadata, metadata_proof, signatures.clone()); - -// match result { -// Result::Ok(_) => panic!("expect 'wrong pre-operation count'"), -// Result::Err(panic_data) => { -// assert(*panic_data.at(0) == 'wrong pre-operation count', *panic_data.at(0)); -// } -// } -// } +// pre - 2 +// post - 1 + +#[test] +#[feature("safe_dispatcher")] +fn test_wrong_post_ops_count() { + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) = + setup_mcms_deploy_set_config_and_set_root(); + + mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + // sign a different set of operations with same signers + + let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = + setup_signers(); + + let op1 = *ops.at(0); + let op1_proof = *ops_proof.at(0); + + let op2 = *ops.at(1); + let op2_proof = *ops_proof.at(1); + + mcms.execute(op1, op1_proof); + mcms.execute(op2, op2_proof); + let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = + generate_set_root_params_custom_op_count( + mcms_address, + contract_address_const::<123123>(), + signer_metadata, + 2, // correct pre-op count + 1 // wrong post-op count + ); + let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + match result { + Result::Ok(_) => panic!("expect 'wrong post-operation count'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'wrong post-operation count', *panic_data.at(0)); + } + } +} diff --git a/contracts/src/tests/test_mcms/utils.cairo b/contracts/src/tests/test_mcms/utils.cairo index 517c7b92f..014ab8309 100644 --- a/contracts/src/tests/test_mcms/utils.cairo +++ b/contracts/src/tests/test_mcms/utils.cairo @@ -16,68 +16,54 @@ use starknet::{ }, secp256k1::{Secp256k1Point, Secp256k1Impl}, SyscallResult, SyscallResultTrait }; - use chainlink::mcms::{ - ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, Op, + recover_eth_ecdsa, hash_pair, hash_op, hash_metadata, ExpiringRootAndOpCount, RootMetadata, + Config, Signer, eip_191_message_hash, ManyChainMultiSig, Op, ManyChainMultiSig::{ - MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP, MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA, - InternalFunctionsTrait, contract_state_for_testing, s_signersContractMemberStateTrait, - s_expiring_root_and_op_countContractMemberStateTrait, + NewRoot, InternalFunctionsTrait, contract_state_for_testing, + s_signersContractMemberStateTrait, s_expiring_root_and_op_countContractMemberStateTrait, s_root_metadataContractMemberStateTrait }, IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, ManyChainMultiSig::{MAX_NUM_SIGNERS}, }; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, + stop_cheat_caller_address, stop_cheat_caller_address_global, spy_events, + EventSpyAssertionsTrait, // Add for assertions on the EventSpy + test_address, // the contract being tested, + start_cheat_chain_id, start_cheat_chain_id_global, + start_cheat_block_timestamp_global, cheatcodes::{events::{EventSpy}} +}; -fn hash_op(op: Op) -> u256 { - let mut encoded_leaf: Bytes = BytesTrait::new_empty() - .encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP) - .encode(op.chain_id) - .encode(op.multisig) - .encode(op.nonce) - .encode(op.to) - .encode(op.selector); - // encode the data field by looping through - let mut i = 0; - while i < op.data.len() { - encoded_leaf = encoded_leaf.encode(*op.data.at(i)); - i += 1; - }; - encoded_leaf.keccak() -} +// +// setup helpers +// -fn hash_metadata(metadata: RootMetadata, valid_until: u32) -> u256 { - let encoded_metadata: Bytes = BytesTrait::new_empty() - .encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA) - .encode(valid_until) - .encode(metadata.chain_id) - .encode(metadata.multisig) - .encode(metadata.pre_op_count) - .encode(metadata.post_op_count) - .encode(metadata.override_previous_root); - - encoded_metadata.keccak() +#[derive(Copy, Drop, Serde)] +struct SignerMetadata { + address: EthAddress, + private_key: u256 } -// efficient exponentiation -fn pow(base: u256, exponent: u256, modulo: u256) -> u256 { - if exponent == 0 { - 0 - } else if exponent == 1 { - base % modulo - } else { - let mut t = pow(base, exponent / 2, modulo); - t = (t * t) % modulo; - - if exponent % 2 == 0 { - t - } else { - ((base % modulo) * t) % modulo - } - } -} +fn setup_signers() -> (EthAddress, u256, EthAddress, u256, Array) { + let signer_address_1: EthAddress = (0x13Cf92228941e27eBce80634Eba36F992eCB148A) + .try_into() + .unwrap(); + let private_key_1: u256 = 0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688; + let signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) + .try_into() + .unwrap(); + let private_key_2: u256 = 0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527; + + let signer_metadata = array![ + SignerMetadata { address: signer_address_1, private_key: private_key_1 }, + SignerMetadata { address: signer_address_2, private_key: private_key_2 } + ]; + (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) +} impl U512PartialOrd of PartialOrd { #[inline(always)] @@ -144,7 +130,7 @@ fn insecure_sign(z: u256, e: u256) -> (u256, u256, bool) { while sum_u512 >= n_u512 { sum_u512 = u512_sub(sum_u512, n_u512); }; - let sum = sum_u512.try_into().unwrap(); + let sum: u256 = sum_u512.try_into().unwrap(); let s = u256_div_mod_n(sum, k, N.try_into().unwrap()).unwrap(); @@ -155,3 +141,319 @@ fn insecure_sign(z: u256, e: u256) -> (u256, u256, bool) { (r_x, s, y_parity) } +// simplified logic will only work when len(ops) = 2 +// metadata nodes is the last leaf so that len(leafs) = 3 +fn merkle_root(leafs: Array) -> (u256, Span, Span>) { + let mut level: Array = ArrayTrait::new(); + + let metadata = *leafs.at(leafs.len() - 1); + let mut i = 0; + + // we assume metadata is last leaf so we exclude for now + while i < leafs.len() - 1 { + level.append(*leafs.at(i)); + i += 1; + }; + + let mut level = level.span(); // [leaf1, leaf2] + + let proof1 = array![*level.at(1), metadata]; + let proof2 = array![*level.at(0), metadata]; + + // level length is always even (except when it's 1) + while level + .len() > 1 { + let mut i = 0; + let mut new_level: Array = ArrayTrait::new(); + while i < level + .len() { + new_level.append(hash_pair(*(level.at(i)), *level.at(i + 1))); + i += 2 + }; + level = new_level.span(); + }; + + let mut metadata_proof = *level.at(0); + + // based on merkletree.js lib we use, the odd leaf out is not hashed until the very end + let root = hash_pair(*level.at(0), metadata); + + (root, array![metadata_proof].span(), array![proof1.span(), proof2.span()].span()) +} + +fn set_root_args( + mcms_address: ContractAddress, + target_address: ContractAddress, + mut signers_metadata: Array +) -> (u256, u32, RootMetadata, Span, Array, Array, Span>) { + let mock_chain_id = 732; + + // first operation + let selector1 = selector!("set_value"); + let calldata1: Array = array![1234123]; + let op1 = Op { + chain_id: mock_chain_id.into(), + multisig: mcms_address, + nonce: 0, + to: target_address, + selector: selector1, + data: calldata1.span() + }; + + // second operation + let selector2 = selector!("flip_toggle"); + let calldata2 = array![]; + let op2 = Op { + chain_id: mock_chain_id.into(), + multisig: mcms_address, + nonce: 1, + to: target_address, + selector: selector2, + data: calldata2.span() + }; + + let metadata = RootMetadata { + chain_id: mock_chain_id.into(), + multisig: mcms_address, + pre_op_count: 0, + post_op_count: 2, + override_previous_root: false, + }; + let valid_until = 9; + + let op1_hash = hash_op(op1); + let op2_hash = hash_op(op2); + + let metadata_hash = hash_metadata(metadata, valid_until); + + // create merkle tree + let (root, metadata_proof, ops_proof) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); + + let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); + let message_hash = eip_191_message_hash(encoded_root.keccak()); + + let mut signatures: Array = ArrayTrait::new(); + + while let Option::Some(signer_metadata) = signers_metadata + .pop_front() { + let (r, s, y_parity) = insecure_sign(message_hash, signer_metadata.private_key); + let signature = Signature { r: r, s: s, y_parity: y_parity }; + let address = recover_eth_ecdsa(message_hash, signature).unwrap(); + + // sanity check + assert(address == signer_metadata.address, 'signer not equal'); + + signatures.append(signature); + }; + + let ops = array![op1.clone(), op2.clone()]; + + (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) +} + +// +// setup functions +// + +fn setup_mcms_deploy() -> ( + ContractAddress, IManyChainMultiSigDispatcher, IManyChainMultiSigSafeDispatcher +) { + let calldata = array![]; + + let (mcms_address, _) = declare("ManyChainMultiSig").unwrap().deploy(@calldata).unwrap(); + + ( + mcms_address, + IManyChainMultiSigDispatcher { contract_address: mcms_address }, + IManyChainMultiSigSafeDispatcher { contract_address: mcms_address } + ) +} + +fn setup_mcms_deploy_and_set_config_2_of_2( + signer_address_1: EthAddress, signer_address_2: EthAddress +) -> ( + EventSpy, + ContractAddress, + IManyChainMultiSigDispatcher, + IManyChainMultiSigSafeDispatcher, + Config, + Array, + Array, + Array, + Array, + bool +) { + let (mcms_address, mcms, safe_mcms) = setup_mcms_deploy(); + + let signer_addresses: Array = array![signer_address_1, signer_address_2]; + let signer_groups = array![0, 0]; + let group_quorums = array![ + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ]; + let group_parents = array![ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ]; + let clear_root = false; + + let mut spy = spy_events(); + + mcms + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + let config = mcms.get_config(); + + ( + spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root + ) +} + +// sets up root +fn setup_mcms_deploy_set_config_and_set_root() -> ( + EventSpy, + ContractAddress, + IManyChainMultiSigDispatcher, + IManyChainMultiSigSafeDispatcher, + Config, + Array, + Array, + Array, + Array, + bool, // clear root + u256, + u32, + RootMetadata, + Span, + Array, + Array, + Span> +) { + let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = + setup_signers(); + + let ( + mut spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root + ) = + setup_mcms_deploy_and_set_config_2_of_2( + signer_address_1, signer_address_2 + ); + + let calldata = ArrayTrait::new(); + let mock_target_contract = declare("MockMultisigTarget").unwrap(); + let (target_address, _) = mock_target_contract.deploy(@calldata).unwrap(); + + let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = set_root_args( + mcms_address, target_address, signer_metadata + ); + + // mock chain id & timestamp + start_cheat_chain_id_global(metadata.chain_id.try_into().unwrap()); + + let mock_timestamp = 3; + start_cheat_block_timestamp_global(mock_timestamp); + + ( + spy, + mcms_address, + mcms, + safe_mcms, + config, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root, + root, + valid_until, + metadata, + metadata_proof, + signatures, + ops, + ops_proof + ) +}