From ff58ba0ff7bfbea0105d875f3491a89225022205 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 6 Aug 2024 15:53:21 -0400 Subject: [PATCH 01/24] mcms initial --- contracts/src/lib.cairo | 1 + contracts/src/mcms.cairo | 101 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 contracts/src/mcms.cairo diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index eef049d8c..71ffd2336 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -8,6 +8,7 @@ mod emergency; mod multisig; mod token; mod access_control; +mod mcms; #[cfg(test)] mod tests; diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo new file mode 100644 index 000000000..bed175737 --- /dev/null +++ b/contracts/src/mcms.cairo @@ -0,0 +1,101 @@ +use starknet::ContractAddress; +use starknet::{ + EthAddress, + secp256_trait::{ + Secp256Trait, Secp256PointTrait, recover_public_key, is_signature_entry_valid, Signature + }, + secp256k1::Secp256k1Point, SyscallResult, SyscallResultTrait +}; + +#[starknet::interface] +trait IManyChainMultiSig { + // todo: check length of byte array is 32 + fn set_root( + ref self: TContractState, + root: u256, + valid_until: u32, + metadata: RootMetadata, + metadata_proof: Span, + // note: v is a boolean and not uint8 + signaures: Span + ); + fn execute(ref self: TContractState, op: Op, proof: Span); + // todo: check length of group_quorums and group_parents + fn set_config( + ref self: TContractState, + signer_addresses: Span, + signer_groups: Span, + group_quorums: Span, + group_parents: Span, + clear_root: bool + ); + fn get_config(self: @TContractState) -> Config; + fn get_op_count(self: @TContractState) -> u64; + fn get_root(self: @TContractState) -> (u256, u32); + fn get_root_metadata(self: @TContractState) -> RootMetadata; +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Signer { + address: EthAddress, + index: u8, + group: u8 +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct RootMetadata { + chain_id: u256, + multisig: ContractAddress, + pre_opcount: u64, + post_opcount: u64, + override_previous_root: bool +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Op { + chain_id: u256, + multisig: ContractAddress, + nonce: u64, + to: ContractAddress, + data: ByteArray +} + +// does not implement Storage trait because structs cannot support arrays or maps +#[derive(Copy, Drop, Serde)] +struct Config { + signers: Span, + group_quorums: Span, + group_parents: Span +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct ExpiringRootAndOpCount { + root: u256, + valid_until: u32, + op_count: u64 +} + + +#[starknet::contract] +mod ManyChainMultiSig { + use super::{ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op}; + use starknet::{EthAddress}; + const NUM_GROUPS: u8 = 32; + const MAX_NUM_SIGNERS: u8 = 200; + + #[storage] + struct Storage { + s_signers: LegacyMap, + // begin s_config (defined in storage bc Config struct cannot support maps) + _s_config_signers_len: u8, + _s_config_signers: LegacyMap, + // no _s_config_group_len because there are always 32 groups + _s_config_group_quorums: LegacyMap, + _s_config_group_parents: LegacyMap, + // end s_config + s_seen_signed_hashes: LegacyMap, + s_expiring_root_and_op_count: ExpiringRootAndOpCount, + s_root_metadata: RootMetadata + } +} + From cda1cf6c39e01abaca465324cc1a378b2e3c9ada Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Thu, 8 Aug 2024 16:25:51 -0400 Subject: [PATCH 02/24] finish set_root --- .vscode/settings.json | 2 +- contracts/Scarb.lock | 54 +++++++++ contracts/Scarb.toml | 3 + contracts/src/mcms.cairo | 243 +++++++++++++++++++++++++++++++++++---- 4 files changed, 281 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bb4c99574..3f77e7328 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "editor.formatOnSave": true, "editor.formatOnSaveTimeout": 1500, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "files.insertFinalNewline": true } diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 88a112553..cfdbfc0f4 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -1,10 +1,64 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "alexandria_bytes" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + +[[package]] +name = "alexandria_data_structures" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_bytes", + "alexandria_math", + "alexandria_numeric", +] + +[[package]] +name = "alexandria_math" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_data_structures", +] + +[[package]] +name = "alexandria_numeric" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_math", + "alexandria_searching", +] + +[[package]] +name = "alexandria_searching" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_data_structures", +] + [[package]] name = "chainlink" version = "0.1.0" dependencies = [ + "alexandria_bytes", + "alexandria_encoding", "openzeppelin", ] diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 69c519dfd..4a89900c9 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -14,6 +14,9 @@ sierra = "cairo-compile . -r" [dependencies] starknet = ">=2.6.3" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" } +alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } +alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } + [lib] diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index bed175737..56b723399 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -1,11 +1,14 @@ use starknet::ContractAddress; use starknet::{ - EthAddress, + eth_signature::public_key_point_to_eth_address, EthAddress, secp256_trait::{ Secp256Trait, Secp256PointTrait, recover_public_key, is_signature_entry_valid, Signature }, secp256k1::Secp256k1Point, SyscallResult, SyscallResultTrait }; +use alexandria_bytes::{Bytes, BytesTrait}; +use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; +use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; #[starknet::interface] trait IManyChainMultiSig { @@ -17,22 +20,22 @@ trait IManyChainMultiSig { metadata: RootMetadata, metadata_proof: Span, // note: v is a boolean and not uint8 - signaures: Span + signatures: Array ); fn execute(ref self: TContractState, op: Op, proof: Span); - // todo: check length of group_quorums and group_parents - fn set_config( - ref self: TContractState, - signer_addresses: Span, - signer_groups: Span, - group_quorums: Span, - group_parents: Span, - clear_root: bool - ); - fn get_config(self: @TContractState) -> Config; - fn get_op_count(self: @TContractState) -> u64; - fn get_root(self: @TContractState) -> (u256, u32); - fn get_root_metadata(self: @TContractState) -> RootMetadata; +// // todo: check length of group_quorums and group_parents +// fn set_config( +// ref self: TContractState, +// signer_addresses: Span, +// signer_groups: Span, +// group_quorums: Span, +// group_parents: Span, +// clear_root: bool +// ); +// fn get_config(self: @TContractState) -> Config; +// fn get_op_count(self: @TContractState) -> u64; +// fn get_root(self: @TContractState) -> (u256, u32); +// fn get_root_metadata(self: @TContractState) -> RootMetadata; } #[derive(Copy, Drop, Serde, starknet::Store)] @@ -46,12 +49,13 @@ struct Signer { struct RootMetadata { chain_id: u256, multisig: ContractAddress, - pre_opcount: u64, - post_opcount: u64, + pre_op_count: u64, + post_op_count: u64, override_previous_root: bool } -#[derive(Copy, Drop, Serde, starknet::Store)] +// todo: maybe use copy +#[derive(Drop, Serde, starknet::Store)] struct Op { chain_id: u256, multisig: ContractAddress, @@ -75,13 +79,69 @@ struct ExpiringRootAndOpCount { op_count: u64 } +// based of https://github.com/starkware-libs/cairo/blob/1b747da1ec7e43a6fd0c0a4cbce302616408bc72/corelib/src/starknet/eth_signature.cairo#L25 +pub fn recover_eth_ecdsa(msg_hash: u256, signature: Signature) -> Result { + if !is_signature_entry_valid::(signature.r) { + return Result::Err('Signature out of range'); + } + if !is_signature_entry_valid::(signature.s) { + return Result::Err('Signature out of range'); + } + + let public_key_point = recover_public_key::(:msg_hash, :signature).unwrap(); + // calculated eth address + return Result::Ok(public_key_point_to_eth_address(:public_key_point)); +} + +pub fn to_u256(address: EthAddress) -> u256 { + let temp: felt252 = address.into(); + temp.into() +} + +pub fn verify_merkle_proof(proof: Span, root: u256, leaf: u256) -> bool { + let mut computed_hash = leaf; + + let mut i = 0; + + while i < proof.len() { + computed_hash = hash_pair(computed_hash, *proof.at(i)); + i += 1; + }; + + computed_hash == root +} + +fn hash_pair(a: u256, b: u256) -> u256 { + let (lower, higher) = if a < b { + (a, b) + } else { + (b, a) + }; + BytesTrait::new_empty().encode(lower).encode(higher).keccak() +} #[starknet::contract] mod ManyChainMultiSig { - use super::{ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op}; - use starknet::{EthAddress}; + use core::dict::Felt252Dict; + use core::traits::PanicDestruct; + use super::{ + ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op, Signature, recover_eth_ecdsa, + to_u256, verify_merkle_proof + }; + use starknet::{EthAddress, EthAddressZeroable, EthAddressIntoFelt252}; + use starknet::eth_signature::is_eth_signature_valid; + use alexandria_bytes::{Bytes, BytesTrait}; + use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; + use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; + const NUM_GROUPS: u8 = 32; const MAX_NUM_SIGNERS: u8 = 200; + // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP") + const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPERATOR_OP: u256 = + 0x08d275622006c4ca82d03f498e90163cafd53c663a48470c3b52ac8bfbd9f52c; + // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA") + const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = + 0xe6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c; #[storage] struct Storage { @@ -97,5 +157,148 @@ mod ManyChainMultiSig { s_expiring_root_and_op_count: ExpiringRootAndOpCount, s_root_metadata: RootMetadata } + + #[derive(Drop, starknet::Event)] + struct NewRoot { + #[key] + root: u256, + valid_until: u32, + metadata: RootMetadata, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + NewRoot: NewRoot + } + + + #[abi(embed_v0)] + impl ManyChainMultiSigImpl of super::IManyChainMultiSig { + fn set_root( + ref self: ContractState, + root: u256, + valid_until: u32, + metadata: RootMetadata, + metadata_proof: Span, + // note: v is a boolean and not uint8 + mut signatures: Array + ) { + let encoded_root: Bytes = BytesTrait::new_empty().encode(root).encode(valid_until); + + let mut eip_191_msg: Bytes = BytesTrait::new_empty(); + eip_191_msg.append_felt252('\x19Ethereum Signed Message:\n32'); + eip_191_msg.append_u256(encoded_root.keccak()); + let msg_hash = eip_191_msg.keccak(); + + assert(!self.s_seen_signed_hashes.read(msg_hash), 'signed hash already seen'); + + let prev_address = EthAddressZeroable::zero(); + let mut group_vote_counts: Felt252Dict = Default::default(); + while let Option::Some(signature) = signatures + .pop_front() { + let signer_address = match recover_eth_ecdsa(msg_hash, signature) { + Result::Ok(signer_address) => signer_address, + Result::Err(e) => panic_with_felt252(e), + }; + + assert( + to_u256(prev_address) < to_u256(signer_address), + 'signer address must increase' + ); + + let signer = self.s_signers.read(signer_address); + assert(signer.address == signer_address, 'invalid signer'); + + let mut group = signer.group; + loop { + // todo: may be unnecessary assert + assert(group < NUM_GROUPS, 'invalid group number'); + let counts = group_vote_counts.get(group.into()); + group_vote_counts.insert(group.into(), counts + 1); + if counts + 1 != self._s_config_group_quorums.read(group) { + break; + } + if group == 0 { + // reached root + break; + } + group = self._s_config_group_parents.read(group) + }; + }; + + let root_group_quorum = self._s_config_group_quorums.read(0); + assert(root_group_quorum > 0, 'missing config'); + assert(group_vote_counts.get(0) >= root_group_quorum, 'insufficient signers'); + assert( + valid_until.into() >= starknet::info::get_block_timestamp(), + 'valid until has passed' + ); + // verify metadataProof + // todo: make sure this is the right way to encode the struct + 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); + + let hashed_leaf = encoded_metadata.keccak(); + assert( + verify_merkle_proof(metadata_proof, root, hashed_leaf), 'proof verification failed' + ); + + // maybe move to beginning of function + assert( + starknet::info::get_tx_info().unbox().chain_id.into() == metadata.chain_id, + 'wrong chain id' + ); + assert( + starknet::info::get_contract_address() == metadata.multisig, + 'wrong multisig address' + ); + + let op_count = self.s_expiring_root_and_op_count.read().op_count; + let current_root_metadata = self.s_root_metadata.read(); + assert( + op_count == current_root_metadata.post_op_count + || current_root_metadata.override_previous_root, + 'expect pending operations' + ); + assert(op_count == metadata.pre_op_count, 'wrong pre-operation count'); + assert(metadata.pre_op_count <= metadata.post_op_count, 'wrong post-operation count'); + + self.s_seen_signed_hashes.write(msg_hash, true); + self + .s_expiring_root_and_op_count + .write( + ExpiringRootAndOpCount { + root: root, valid_until: valid_until, op_count: metadata.pre_op_count + } + ); + + self + .emit( + Event::NewRoot( + NewRoot { root: root, valid_until: valid_until, metadata: metadata, } + ) + ); + } + + fn execute(ref self: ContractState, op: Op, proof: Span) { + let current_expiring_root_and_op_count = self.s_expiring_root_and_op_count.read(); + + assert( + self + .s_root_metadata + .read() + .post_op_count > current_expiring_root_and_op_count + .op_count, + 'post-operation count reached' + ); + } + } } From c36a52d69785bcd07a80f72539564e7415526525 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 9 Aug 2024 10:58:13 -0400 Subject: [PATCH 03/24] update mcms --- contracts/src/mcms.cairo | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 56b723399..1216a35a3 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -298,6 +298,13 @@ mod ManyChainMultiSig { .op_count, 'post-operation count reached' ); + + assert(starknet::info::get_tx_info().unbox().chain_id.into() == op.chain_id, 'wrong chain id'); + + assert( + starknet::info::get_contract_address() == op.multisig, + 'wrong multisig address' + ); } } } From f6208568bc75e13ceda13f25c52bcbbf2bd9584c Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 9 Aug 2024 17:14:25 -0400 Subject: [PATCH 04/24] wip --- contracts/src/mcms.cairo | 70 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 1216a35a3..a283153c5 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -55,12 +55,14 @@ struct RootMetadata { } // todo: maybe use copy +// todo: figure out how this works off-chain with MCMS since we have a new selector field here #[derive(Drop, Serde, starknet::Store)] struct Op { chain_id: u256, multisig: ContractAddress, nonce: u64, to: ContractAddress, + selector: felt252, data: ByteArray } @@ -128,7 +130,7 @@ mod ManyChainMultiSig { ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op, Signature, recover_eth_ecdsa, to_u256, verify_merkle_proof }; - use starknet::{EthAddress, EthAddressZeroable, EthAddressIntoFelt252}; + use starknet::{EthAddress, EthAddressZeroable, EthAddressIntoFelt252, ContractAddress}; use starknet::eth_signature::is_eth_signature_valid; use alexandria_bytes::{Bytes, BytesTrait}; use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; @@ -137,7 +139,7 @@ mod ManyChainMultiSig { const NUM_GROUPS: u8 = 32; const MAX_NUM_SIGNERS: u8 = 200; // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP") - const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPERATOR_OP: u256 = + const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP: u256 = 0x08d275622006c4ca82d03f498e90163cafd53c663a48470c3b52ac8bfbd9f52c; // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA") const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = @@ -166,10 +168,20 @@ mod ManyChainMultiSig { metadata: RootMetadata, } + #[derive(Drop, starknet::Event)] + struct OpExecuted { + #[key] + nonce: u64, + to: ContractAddress, + data: ByteArray + // no value because value is sent through ERC20 tokens, even the native STRK token + } + #[event] #[derive(Drop, starknet::Event)] enum Event { - NewRoot: NewRoot + NewRoot: NewRoot, + OpExecuted: OpExecuted, } @@ -299,13 +311,57 @@ mod ManyChainMultiSig { 'post-operation count reached' ); - assert(starknet::info::get_tx_info().unbox().chain_id.into() == op.chain_id, 'wrong chain id'); - assert( - starknet::info::get_contract_address() == op.multisig, - 'wrong multisig address' + starknet::info::get_tx_info().unbox().chain_id.into() == op.chain_id, + 'wrong chain id' ); + + assert(starknet::info::get_contract_address() == op.multisig, 'wrong multisig address'); + + assert( + current_expiring_root_and_op_count + .valid_until + .into() >= starknet::info::get_block_timestamp(), + 'root has expired' + ); + + assert(op.nonce == current_expiring_root_and_op_count.op_count, 'wrong nonce'); + + // verify op exists in merkle tree + let 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) // todo: selector doesn't currently exist + .encode(op.data.clone()); + + let hashed_leaf = encoded_leaf.keccak(); + + assert( + verify_merkle_proof(proof, current_expiring_root_and_op_count.root, hashed_leaf), + 'proof verification failed' + ); + + let mut new_expiring_root_and_op_count = current_expiring_root_and_op_count; + 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 + .emit( + Event::OpExecuted( + OpExecuted { nonce: op.nonce, to: op.to, data: op.data.clone() } + ) + ); } } + + #[generate_trait] + impl InternalFunctions of InternalFunctionsTrait { + fn _execute(target: ContractAddress, ) + } } From 43c5b9e92a5cd8071876560c7bc8e285816df653 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Mon, 26 Aug 2024 11:59:32 -0400 Subject: [PATCH 05/24] midway --- contracts/src/mcms.cairo | 79 ++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index a283153c5..95df9625b 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -23,15 +23,15 @@ trait IManyChainMultiSig { signatures: Array ); fn execute(ref self: TContractState, op: Op, proof: Span); -// // todo: check length of group_quorums and group_parents -// fn set_config( -// ref self: TContractState, -// signer_addresses: Span, -// signer_groups: Span, -// group_quorums: Span, -// group_parents: Span, -// clear_root: bool -// ); + // // todo: check length of group_quorums and group_parents + fn set_config( + ref self: TContractState, + signer_addresses: Span, + signer_groups: Span, + group_quorums: Span, + group_parents: Span, + clear_root: bool + ); // fn get_config(self: @TContractState) -> Config; // fn get_op_count(self: @TContractState) -> u64; // fn get_root(self: @TContractState) -> (u256, u32); @@ -56,14 +56,14 @@ struct RootMetadata { // todo: maybe use copy // todo: figure out how this works off-chain with MCMS since we have a new selector field here -#[derive(Drop, Serde, starknet::Store)] +#[derive(Copy, Drop, Serde)] struct Op { chain_id: u256, multisig: ContractAddress, nonce: u64, to: ContractAddress, selector: felt252, - data: ByteArray + data: Span } // does not implement Storage trait because structs cannot support arrays or maps @@ -124,13 +124,18 @@ fn hash_pair(a: u256, b: u256) -> u256 { #[starknet::contract] mod ManyChainMultiSig { + use core::starknet::SyscallResultTrait; + use core::array::SpanTrait; use core::dict::Felt252Dict; use core::traits::PanicDestruct; use super::{ ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op, Signature, recover_eth_ecdsa, to_u256, verify_merkle_proof }; - use starknet::{EthAddress, EthAddressZeroable, EthAddressIntoFelt252, ContractAddress}; + use starknet::{ + EthAddress, EthAddressZeroable, EthAddressIntoFelt252, ContractAddress, + call_contract_syscall + }; use starknet::eth_signature::is_eth_signature_valid; use alexandria_bytes::{Bytes, BytesTrait}; use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; @@ -173,7 +178,8 @@ mod ManyChainMultiSig { #[key] nonce: u64, to: ContractAddress, - data: ByteArray + selector: felt252, + data: Span // no value because value is sent through ERC20 tokens, even the native STRK token } @@ -328,15 +334,19 @@ mod ManyChainMultiSig { assert(op.nonce == current_expiring_root_and_op_count.op_count, 'wrong nonce'); // verify op exists in merkle tree - let encoded_leaf: Bytes = BytesTrait::new_empty() + 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) // todo: selector doesn't currently exist - .encode(op.data.clone()); - + .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; + }; let hashed_leaf = encoded_leaf.keccak(); assert( @@ -349,19 +359,50 @@ mod ManyChainMultiSig { 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 .emit( Event::OpExecuted( - OpExecuted { nonce: op.nonce, to: op.to, data: op.data.clone() } + OpExecuted { + nonce: op.nonce, to: op.to, selector: op.selector, data: op.data + } ) ); } + + // todo: make onlyOwner + fn set_config( + ref self: ContractState, + signer_addresses: Span, + signer_groups: Span, + group_quorums: Span, + group_parents: Span, + clear_root: bool + ) { + assert( + signer_addresses.len() != 0 && signer_addresses.len() <= MAX_NUM_SIGNERS.into(), + 'out of bound signers len' + ); + + assert(signer_addresses.len() == signer_groups.len(), 'signer groups len mismatch'); + + assert( + group_quorums.len() == NUM_GROUPS.into() + && group_quorums.len() == group_parents.len(), + 'group quorums/parents mismatch' + ); + // let mut group_children_counts = + } } #[generate_trait] impl InternalFunctions of InternalFunctionsTrait { - fn _execute(target: ContractAddress, ) + fn _execute( + ref self: ContractState, target: ContractAddress, selector: felt252, data: Span + ) { + let _response = call_contract_syscall(target, selector, data).unwrap_syscall(); + } } } From f7249c6aabcf82184ac2281068c54137f66654e4 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Thu, 29 Aug 2024 10:28:48 -0400 Subject: [PATCH 06/24] in progress - set root --- contracts/src/mcms.cairo | 129 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 95df9625b..d09dcd4a2 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -149,9 +149,11 @@ mod ManyChainMultiSig { // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA") const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = 0xe6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c; + const S_CONFIG_GROUP_LEN: u8 = NUM_GROUPS; #[storage] struct Storage { + // s_signers is used to easily validate the existence of the signer by its address. s_signers: LegacyMap, // begin s_config (defined in storage bc Config struct cannot support maps) _s_config_signers_len: u8, @@ -179,15 +181,22 @@ mod ManyChainMultiSig { nonce: u64, to: ContractAddress, selector: felt252, - data: Span + data: Span, // no value because value is sent through ERC20 tokens, even the native STRK token } + #[derive(Drop, starknet::Event)] + struct ConfigSet { + config: Config, + is_root_cleared: bool, + } + #[event] #[derive(Drop, starknet::Event)] enum Event { NewRoot: NewRoot, OpExecuted: OpExecuted, + ConfigSet: ConfigSet, } @@ -392,7 +401,123 @@ mod ManyChainMultiSig { && group_quorums.len() == group_parents.len(), 'group quorums/parents mismatch' ); - // let mut group_children_counts = + + let mut group_children_counts: Felt252Dict = Default::default(); + let mut i = 0; + while i < signer_groups + .len() { + let group = *signer_groups.at(i); + assert(group < NUM_GROUPS, 'out of bounds group'); + // increment count for each group + group_children_counts + .insert(group.into(), group_children_counts.get(group.into()) + 1); + i += 1; + }; + + let mut j = 0; + while j < NUM_GROUPS { + // iterate backwards: i is the group # + let i = NUM_GROUPS - 1 - j; + let group_parent = (*group_parents.at(i.into())); + + assert( + (i == 0 || group_parent < i) && (i != 0 || group_parent == 0), + 'group tree malformed' + ); + + let disabled = *group_quorums.at(i.into()) == 0; + + if disabled { + assert(group_children_counts.get(i.into()) == 0, 'signer in disabled group'); + } else { + assert( + group_children_counts.get(i.into()) >= *group_quorums.at(i.into()), + 'quorum impossible' + ); + + group_children_counts + .insert( + group_parent.into(), group_children_counts.get(group_parent.into()) + 1 + ); + // the above line clobbers group_children_counts[0] in last iteration, don't use it after the loop ends + } + j += 1; + }; + // SDFSDF:Kj + // remove any old signer addresses + let mut i: u8 = 0; + let signers_len = self._s_config_signers_len.read(); + + // create signers array (for event e) + while i < signers_len { + let mut old_signer = self._s_config_signers.read(i); + let empty_signer = Signer { + address: EthAddressZeroable::zero(), index: 0, group: 0 + }; + // reset s_signers + self.s_signers.write(old_signer.address, empty_signer); + // reset _s_config_signers + self._s_config_signers.write(i.into(), empty_signer); + }; + // reset _s_config_signers_len + self._s_config_signers_len.write(0); + + let mut i: u8 = 0; + while i < NUM_GROUPS { + self._s_config_group_quorums.write(i, *group_quorums.at(i.into())); + self._s_config_group_parents.write(i, *group_parents.at(i.into())); + i += 1; + }; + + let mut prev_signer_address = EthAddressZeroable::zero(); + let mut i: u8 = 0; + while i + .into() < signer_addresses + .len() { + let signer_address = *signer_addresses.at(i.into()); + assert( + to_u256(prev_signer_address) < to_u256(signer_address), + 'addresses not sorted' + ); + + let signer = Signer { + address: signer_address, index: i, group: *signer_groups.at(i.into()) + }; + + self.s_signers.write(signer_address, signer); + self._s_config_signers.write(i.into(), signer); + + prev_signer_address = signer_address; + i += 1; + }; + + if clear_root { + let op_count = self.s_expiring_root_and_op_count.read().op_count; + self + .s_expiring_root_and_op_count + .write(ExpiringRootAndOpCount { root: 0, valid_until: 0, op_count: op_count }); + self + .s_root_metadata + .write( + RootMetadata { + chain_id: starknet::info::get_tx_info().unbox().chain_id.into(), + multisig: starknet::info::get_contract_address(), + pre_op_count: op_count, + post_op_count: op_count, + override_previous_root: true + } + ); + } + + // self + // .emit( + // Event::ConfigSet( + // ConfigSet { config: Config { + // signers: + // }, is_root_cleared: clear_root } + // ) + // ); + } } From eae6c6a3475319c9b6683078f915da61e68268b1 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Thu, 29 Aug 2024 11:57:48 -0400 Subject: [PATCH 07/24] mcm done --- contracts/src/mcms.cairo | 78 ++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index d09dcd4a2..3f3e8d613 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -32,10 +32,10 @@ trait IManyChainMultiSig { group_parents: Span, clear_root: bool ); -// fn get_config(self: @TContractState) -> Config; -// fn get_op_count(self: @TContractState) -> u64; -// fn get_root(self: @TContractState) -> (u256, u32); -// fn get_root_metadata(self: @TContractState) -> RootMetadata; + fn get_config(self: @TContractState) -> Config; + fn get_op_count(self: @TContractState) -> u64; + fn get_root(self: @TContractState) -> (u256, u32); + fn get_root_metadata(self: @TContractState) -> RootMetadata; } #[derive(Copy, Drop, Serde, starknet::Store)] @@ -124,6 +124,7 @@ fn hash_pair(a: u256, b: u256) -> u256 { #[starknet::contract] mod ManyChainMultiSig { + use core::array::ArrayTrait; use core::starknet::SyscallResultTrait; use core::array::SpanTrait; use core::dict::Felt252Dict; @@ -142,6 +143,7 @@ mod ManyChainMultiSig { use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; const NUM_GROUPS: u8 = 32; + const MAX_NUM_SIGNERS: u8 = 200; // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP") const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP: u256 = @@ -149,7 +151,6 @@ mod ManyChainMultiSig { // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA") const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = 0xe6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c; - const S_CONFIG_GROUP_LEN: u8 = NUM_GROUPS; #[storage] struct Storage { @@ -443,12 +444,11 @@ mod ManyChainMultiSig { } j += 1; }; - // SDFSDF:Kj + // remove any old signer addresses let mut i: u8 = 0; let signers_len = self._s_config_signers_len.read(); - // create signers array (for event e) while i < signers_len { let mut old_signer = self._s_config_signers.read(i); let empty_signer = Signer { @@ -469,6 +469,8 @@ mod ManyChainMultiSig { i += 1; }; + // create signers array (for event logs) + let mut signers = ArrayTrait::::new(); let mut prev_signer_address = EthAddressZeroable::zero(); let mut i: u8 = 0; while i @@ -487,6 +489,8 @@ mod ManyChainMultiSig { self.s_signers.write(signer_address, signer); self._s_config_signers.write(i.into(), signer); + signers.append(signer); + prev_signer_address = signer_address; i += 1; }; @@ -509,15 +513,59 @@ mod ManyChainMultiSig { ); } - // self - // .emit( - // Event::ConfigSet( - // ConfigSet { config: Config { - // signers: - // }, is_root_cleared: clear_root } - // ) - // ); + self + .emit( + Event::ConfigSet( + ConfigSet { + config: Config { + signers: signers.span(), + group_quorums: group_quorums, + group_parents: group_parents, + }, + is_root_cleared: clear_root + } + ) + ); + } + + fn get_config(self: @ContractState) -> Config { + let mut signers = ArrayTrait::::new(); + + let mut i = 0; + let signers_len = self._s_config_signers_len.read(); + while i < signers_len { + let signer = self._s_config_signers.read(i); + signers.append(signer); + i += 1 + }; + + let mut group_quorums = ArrayTrait::::new(); + let mut group_parents = ArrayTrait::::new(); + + let mut i = 0; + while i < NUM_GROUPS { + group_quorums.append(self._s_config_group_quorums.read(i)); + group_parents.append(self._s_config_group_parents.read(i)); + }; + + Config { + signers: signers.span(), + group_quorums: group_quorums.span(), + group_parents: group_parents.span() + } + } + + fn get_op_count(self: @ContractState) -> u64 { + self.s_expiring_root_and_op_count.read().op_count + } + + fn get_root(self: @ContractState) -> (u256, u32) { + let current = self.s_expiring_root_and_op_count.read(); + (current.root, current.valid_until) + } + fn get_root_metadata(self: @ContractState) -> RootMetadata { + self.s_root_metadata.read() } } From 44bee96e31d0e74840aab998055291c2ef1b5586 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Thu, 29 Aug 2024 18:31:39 -0400 Subject: [PATCH 08/24] fix examples with updated foundry --- .tool-versions | 1 + contracts/Scarb.lock | 6 ++ contracts/Scarb.toml | 3 +- contracts/src/tests/test_aggregator.cairo | 2 + contracts/src/tests/test_mcms.cairo | 15 +++++ .../contracts/aggregator_consumer/Scarb.lock | 59 ++++++++++++++++++- .../contracts/aggregator_consumer/Scarb.toml | 5 +- .../tests/test_consumer.cairo | 18 +++++- .../test_price_consumer_with_sequencer.cairo | 26 +++++--- 9 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 contracts/src/tests/test_mcms.cairo diff --git a/.tool-versions b/.tool-versions index b00c44d37..e3f251c90 100644 --- a/.tool-versions +++ b/.tool-versions @@ -11,6 +11,7 @@ actionlint 1.6.12 shellcheck 0.8.0 scarb 2.6.5 postgres 15.1 +starknet-foundry 0.27.0 # Kubernetes k3d 5.4.4 diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index cfdbfc0f4..0c0ba3523 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -60,9 +60,15 @@ dependencies = [ "alexandria_bytes", "alexandria_encoding", "openzeppelin", + "snforge_std", ] [[package]] name = "openzeppelin" version = "0.10.0" source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d77082732daab2690ba50742ea41080eb23299d3" + +[[package]] +name = "snforge_std" +version = "0.27.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.27.0#2d99b7c00678ef0363881ee0273550c44a9263de" diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 4a89900c9..261bc54b7 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -7,6 +7,7 @@ homepage = "https://github.com/smartcontractkit/chainlink-starknet" [scripts] sierra = "cairo-compile . -r" +# test = "snforge test" # Add your own custom commands and run them with scarb run # Uncomment if you want to use dependencies @@ -16,7 +17,7 @@ starknet = ">=2.6.3" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" } alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } - +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" } [lib] diff --git a/contracts/src/tests/test_aggregator.cairo b/contracts/src/tests/test_aggregator.cairo index e2c3d81c3..8204bfaf2 100644 --- a/contracts/src/tests/test_aggregator.cairo +++ b/contracts/src/tests/test_aggregator.cairo @@ -25,6 +25,8 @@ use chainlink::token::link_token::LinkToken; use chainlink::tests::test_ownable::should_implement_ownable; use chainlink::tests::test_access_controller::should_implement_access_control; +use snforge_std::{declare, ContractClassTrait}; + #[test] fn test_pow_2_0() { assert(pow(2, 0) == 0x1, 'expected 0x1'); diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo new file mode 100644 index 000000000..7233889b2 --- /dev/null +++ b/contracts/src/tests/test_mcms.cairo @@ -0,0 +1,15 @@ +// 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 +// 5. test if one of signer_group #'s is out of bounds NUM_GROUPS +// 6. test if group_parents[i] is greater than or equal to i (when not 0) there is revert +// 7. test if i is 0 and group_parents[i] != 0 and revert +// 8. test if there is a signer in a group where group_quorum[i] == 0 => revert +// 9. test if there are not enough signers to meet a quorum => revert +// 10. test if signer addresses are not in ascending order +// 11. successful => test without clearing root. test the state of storage variables and that event was emitted + + diff --git a/examples/contracts/aggregator_consumer/Scarb.lock b/examples/contracts/aggregator_consumer/Scarb.lock index 586d04a23..5bdff8546 100644 --- a/examples/contracts/aggregator_consumer/Scarb.lock +++ b/examples/contracts/aggregator_consumer/Scarb.lock @@ -9,11 +9,66 @@ dependencies = [ "snforge_std", ] +[[package]] +name = "alexandria_bytes" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + +[[package]] +name = "alexandria_data_structures" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_bytes", + "alexandria_math", + "alexandria_numeric", +] + +[[package]] +name = "alexandria_math" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_data_structures", +] + +[[package]] +name = "alexandria_numeric" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_math", + "alexandria_searching", +] + +[[package]] +name = "alexandria_searching" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +dependencies = [ + "alexandria_data_structures", +] + [[package]] name = "chainlink" version = "0.1.0" dependencies = [ + "alexandria_bytes", + "alexandria_encoding", "openzeppelin", + "snforge_std", ] [[package]] @@ -23,5 +78,5 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d7 [[package]] name = "snforge_std" -version = "0.21.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.21.0#2996b8c1dd66b2715fc67e69578089f278a46790" +version = "0.27.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.27.0#2d99b7c00678ef0363881ee0273550c44a9263de" diff --git a/examples/contracts/aggregator_consumer/Scarb.toml b/examples/contracts/aggregator_consumer/Scarb.toml index e7868b138..8da76d784 100644 --- a/examples/contracts/aggregator_consumer/Scarb.toml +++ b/examples/contracts/aggregator_consumer/Scarb.toml @@ -8,10 +8,13 @@ name = "aggregator_consumer" version = "0.1.0" cairo-version = "2.6.3" +# [scripts] +# test = "snforge test" + # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.21.0" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" } chainlink = { path = "../../../contracts" } starknet = ">=2.6.3" diff --git a/examples/contracts/aggregator_consumer/tests/test_consumer.cairo b/examples/contracts/aggregator_consumer/tests/test_consumer.cairo index 31d86386c..b7afc0b70 100644 --- a/examples/contracts/aggregator_consumer/tests/test_consumer.cairo +++ b/examples/contracts/aggregator_consumer/tests/test_consumer.cairo @@ -13,13 +13,23 @@ use starknet::ContractAddress; fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(decimals.into()); - return declare("MockAggregator").deploy(@calldata).unwrap(); + + let contract = declare("MockAggregator").unwrap(); + + let (contract_address, _) = contract.deploy(@calldata).unwrap(); + + contract_address } fn deploy_consumer(aggregator_address: ContractAddress) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(aggregator_address.into()); - return declare("AggregatorConsumer").deploy(@calldata).unwrap(); + + let contract = declare("AggregatorConsumer").unwrap(); + + let (contract_address, _) = contract.deploy(@calldata).unwrap(); + + contract_address } #[test] @@ -79,7 +89,9 @@ fn test_set_and_read_answer() { let consumer_dispatcher = IAggregatorConsumerDispatcher { contract_address: consumer_address }; // Let's make sure the AggregatorConsumer was initialized correctly - assert(consumer_dispatcher.read_ocr_address() == mock_aggregator_address, 'Invalid OCR address'); + assert( + consumer_dispatcher.read_ocr_address() == mock_aggregator_address, 'Invalid OCR address' + ); assert(consumer_dispatcher.read_answer() == 0, 'Invalid initial answer'); // No round data has been initialized, so reading the latest round should return no data diff --git a/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo index 9582c8030..9826331de 100644 --- a/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo +++ b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo @@ -1,4 +1,6 @@ -use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; use chainlink::emergency::sequencer_uptime_feed::ISequencerUptimeFeedDispatcherTrait; use chainlink::emergency::sequencer_uptime_feed::ISequencerUptimeFeedDispatcher; @@ -17,14 +19,16 @@ use starknet::ContractAddress; fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(decimals.into()); - return declare("MockAggregator").deploy(@calldata).unwrap(); + let (contract_address, _) = declare("MockAggregator").unwrap().deploy(@calldata).unwrap(); + contract_address } fn deploy_uptime_feed(initial_status: u128, owner_address: ContractAddress) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(initial_status.into()); calldata.append(owner_address.into()); - return declare("SequencerUptimeFeed").deploy(@calldata).unwrap(); + let (contract_address, _) = declare("SequencerUptimeFeed").unwrap().deploy(@calldata).unwrap(); + contract_address } fn deploy_price_consumer( @@ -33,7 +37,11 @@ fn deploy_price_consumer( let mut calldata = ArrayTrait::new(); calldata.append(uptime_feed_address.into()); calldata.append(aggregator_address.into()); - return declare("AggregatorPriceConsumer").deploy(@calldata).unwrap(); + let (contract_address, _) = declare("AggregatorPriceConsumer") + .unwrap() + .deploy(@calldata) + .unwrap(); + contract_address } #[test] @@ -52,7 +60,8 @@ fn test_get_latest_price() { // Adds the price consumer contract to the sequencer uptime feed access control list // which allows the price consumer to call the get_latest_price function - start_prank(CheatTarget::All, owner); + start_cheat_caller_address_global(owner); + // start_prank(CheatTarget::All, owner); IAccessControllerDispatcher { contract_address: uptime_feed_address } .add_access(price_consumer_address); @@ -62,7 +71,8 @@ fn test_get_latest_price() { // a new round is initialized using its initial status as the round's answer, so the // latest price should be the initial status that was passed into the sequencer uptime // feed's constructor. - start_prank(CheatTarget::All, price_consumer_address); + start_cheat_caller_address_global(price_consumer_address); + // start_prank(CheatTarget::All, price_consumer_address); let latest_price = IAggregatorPriceConsumerDispatcher { contract_address: price_consumer_address } @@ -70,7 +80,7 @@ fn test_get_latest_price() { assert(latest_price == init_status, 'latest price is incorrect'); // Now let's update the round - stop_prank(CheatTarget::All); + stop_cheat_caller_address_global(); let answer = 1; let block_num = 12345; let observation_timestamp = 100000; @@ -79,7 +89,7 @@ fn test_get_latest_price() { .set_latest_round_data(answer, block_num, observation_timestamp, transmission_timestamp); // This should now return the updated answer - start_prank(CheatTarget::All, price_consumer_address); + start_cheat_caller_address_global(price_consumer_address); let updated_latest_price = IAggregatorPriceConsumerDispatcher { contract_address: price_consumer_address } From f009b2a89682207e07f2a29a4c5e8d8a8c21e836 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 30 Aug 2024 12:27:44 -0400 Subject: [PATCH 09/24] tests all pass with snforge --- .gitignore | 1 + contracts/src/libraries/mocks.cairo | 1 + .../mocks/mock_multisig_target.cairo | 16 + .../src/tests/test_access_controller.cairo | 22 +- contracts/src/tests/test_aggregator.cairo | 104 ++++-- .../src/tests/test_aggregator_proxy.cairo | 60 +++- contracts/src/tests/test_erc677.cairo | 33 +- contracts/src/tests/test_link_token.cairo | 19 +- .../src/tests/test_mock_aggregator.cairo | 7 +- contracts/src/tests/test_multisig.cairo | 336 +++++++++++------- contracts/src/tests/test_ownable.cairo | 10 +- .../tests/test_sequencer_uptime_feed.cairo | 28 +- contracts/src/tests/test_upgradeable.cairo | 23 +- .../test_price_consumer_with_sequencer.cairo | 8 +- 14 files changed, 436 insertions(+), 232 deletions(-) create mode 100644 contracts/src/libraries/mocks/mock_multisig_target.cairo diff --git a/.gitignore b/.gitignore index 1ec5b7415..bf22810e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.snfoundry_cache .direnv artifacts/ vendor/ diff --git a/contracts/src/libraries/mocks.cairo b/contracts/src/libraries/mocks.cairo index 9a58bab7d..a6e8a7395 100644 --- a/contracts/src/libraries/mocks.cairo +++ b/contracts/src/libraries/mocks.cairo @@ -1,2 +1,3 @@ mod mock_upgradeable; mod mock_non_upgradeable; +mod mock_multisig_target; diff --git a/contracts/src/libraries/mocks/mock_multisig_target.cairo b/contracts/src/libraries/mocks/mock_multisig_target.cairo new file mode 100644 index 000000000..686fa976d --- /dev/null +++ b/contracts/src/libraries/mocks/mock_multisig_target.cairo @@ -0,0 +1,16 @@ +#[starknet::contract] +mod MockMultisigTarget { + use array::ArrayTrait; + + #[storage] + struct Storage {} + + #[abi(per_item)] + #[generate_trait] + impl HelperImpl of HelperTrait { + #[external(v0)] + fn increment(ref self: ContractState, val1: felt252, val2: felt252) -> Array { + array![val1 + 1, val2 + 1] + } + } +} diff --git a/contracts/src/tests/test_access_controller.cairo b/contracts/src/tests/test_access_controller.cairo index 17d570978..5f20b2da2 100644 --- a/contracts/src/tests/test_access_controller.cairo +++ b/contracts/src/tests/test_access_controller.cairo @@ -19,13 +19,19 @@ use chainlink::libraries::access_control::{ IAccessController, IAccessControllerDispatcher, IAccessControllerDispatcherTrait }; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + + fn STATE() -> AccessController::ContractState { AccessController::contract_state_for_testing() } fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<777>(); - set_caller_address(account); + start_cheat_caller_address_global(account); + // set_caller_address(account); account } @@ -44,10 +50,13 @@ fn test_access_control() { // Deploy access controller let calldata = array![owner.into(), // owner ]; - let (accessControllerAddr, _) = deploy_syscall( - AccessController::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (accessControllerAddr, _) = declare("AccessController").unwrap().deploy(@calldata).unwrap(); + + // let (accessControllerAddr, _) = deploy_syscall( + // AccessController::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); should_implement_access_control(accessControllerAddr, owner); } @@ -62,7 +71,8 @@ fn should_implement_access_control(contract_addr: ContractAddress, owner: Contra let contract = IAccessControllerDispatcher { contract_address: contract_addr }; let acc2: ContractAddress = contract_address_const::<2222987765>(); - set_contract_address(owner); // required to call contract as owner + start_cheat_caller_address_global(owner); + // set_contract_address(owner); // required to call contract as owner // access check is enabled by default assert(!contract.has_access(acc2, array![]), 'should not have access'); diff --git a/contracts/src/tests/test_aggregator.cairo b/contracts/src/tests/test_aggregator.cairo index 8204bfaf2..cdf12e938 100644 --- a/contracts/src/tests/test_aggregator.cairo +++ b/contracts/src/tests/test_aggregator.cairo @@ -25,7 +25,9 @@ use chainlink::token::link_token::LinkToken; use chainlink::tests::test_ownable::should_implement_ownable; use chainlink::tests::test_access_controller::should_implement_access_control; -use snforge_std::{declare, ContractClassTrait}; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; #[test] fn test_pow_2_0() { @@ -80,15 +82,23 @@ fn setup() -> ( let acc1: ContractAddress = contract_address_const::<777>(); let acc2: ContractAddress = contract_address_const::<888>(); // set acc1 as default caller - set_caller_address(acc1); + start_cheat_caller_address_global(acc1); + + // set_caller_address(acc1); // deploy billing access controller let calldata = array![acc1.into(), // owner = acc1; ]; - let (billingAccessControllerAddr, _) = deploy_syscall( - AccessController::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) + + let (billingAccessControllerAddr, _) = declare("AccessController") + .unwrap() + .deploy(@calldata) .unwrap(); + + // let (billingAccessControllerAddr, _) = deploy_syscall( + // AccessController::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let billingAccessController = IAccessControllerDispatcher { contract_address: billingAccessControllerAddr }; @@ -97,10 +107,13 @@ fn setup() -> ( let calldata = array![acc1.into(), // minter = acc1; acc1.into(), // owner = acc1; ]; - let (linkTokenAddr, _) = deploy_syscall( - LinkToken::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (linkTokenAddr, _) = declare("LinkToken").unwrap().deploy(@calldata).unwrap(); + + // let (linkTokenAddr, _) = deploy_syscall( + // LinkToken::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let linkToken = ILinkTokenDispatcher { contract_address: linkTokenAddr }; // return accounts, billing access controller, link token @@ -120,10 +133,13 @@ fn test_ownable() { 8, // decimals 123, // description ]; - let (aggregatorAddr, _) = deploy_syscall( - Aggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (aggregatorAddr, _) = declare("Aggregator").unwrap().deploy(@calldata).unwrap(); + + // let (aggregatorAddr, _) = deploy_syscall( + // Aggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); should_implement_ownable(aggregatorAddr, account); } @@ -141,10 +157,13 @@ fn test_access_control() { 8, // decimals 123, // description ]; - let (aggregatorAddr, _) = deploy_syscall( - Aggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (aggregatorAddr, _) = declare("Aggregator").unwrap().deploy(@calldata).unwrap(); + + // let (aggregatorAddr, _) = deploy_syscall( + // Aggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); should_implement_access_control(aggregatorAddr, account); } @@ -171,7 +190,8 @@ fn test_set_billing_access_controller_not_owner() { ); // set billing access controller should revert if caller is not owner - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); BillingImpl::set_billing_access_controller(ref state, billingAccessController.contract_address); } @@ -198,7 +218,8 @@ fn test_set_billing_config_no_access() { gas_base: 1, gas_per_signature: 1, }; - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); BillingImpl::set_billing(ref state, config); } @@ -239,7 +260,8 @@ fn test_set_billing_config_as_acc_with_access() { let (owner, acc2, billingAccessController, _) = setup(); let mut state = STATE(); // grant acc2 access on access controller - set_contract_address(owner); + start_cheat_caller_address_global(owner); + // set_contract_address(owner); billingAccessController.add_access(acc2); Aggregator::constructor( @@ -260,7 +282,8 @@ fn test_set_billing_config_as_acc_with_access() { gas_base: 1, gas_per_signature: 1, }; - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); BillingImpl::set_billing(ref state, config); // check billing config @@ -283,9 +306,9 @@ fn test_set_payees_caller_not_owner() { ); let payees = array![PayeeConfig { transmitter: acc2, payee: acc2, },]; - // set payee should revert if caller is not owner - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); PayeeManagementImpl::set_payees(ref state, payees); } @@ -298,8 +321,8 @@ fn test_set_single_payee() { ); let payees = array![PayeeConfig { transmitter: acc2, payee: acc2, },]; - - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); } @@ -315,8 +338,8 @@ fn test_set_multiple_payees() { PayeeConfig { transmitter: acc2, payee: acc2, }, PayeeConfig { transmitter: owner, payee: owner, }, ]; - - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); } @@ -332,7 +355,8 @@ fn test_transfer_payeeship_caller_not_payee() { let transmitter = contract_address_const::<123>(); let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, owner); } @@ -349,9 +373,11 @@ fn test_transfer_payeeship_to_self() { let transmitter = contract_address_const::<123>(); let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, acc2); } @@ -367,9 +393,11 @@ fn test_accept_payeeship_caller_not_proposed_payee() { let transmitter = contract_address_const::<123>(); let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, owner); PayeeManagementImpl::accept_payeeship(ref state, transmitter); } @@ -385,11 +413,14 @@ fn test_transfer_and_accept_payeeship() { let transmitter = contract_address_const::<123>(); let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); - set_caller_address(acc2); + start_cheat_caller_address_global(acc2); + // set_caller_address(acc2); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, owner); - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::accept_payeeship(ref state, transmitter); } // --- Payments and Withdrawals Tests --- @@ -410,7 +441,8 @@ fn test_owed_payment_no_rounds() { let transmitter = contract_address_const::<123>(); let mut payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; - set_caller_address(owner); + start_cheat_caller_address_global(owner); + // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); let owed = BillingImpl::owed_payment(@state, transmitter); diff --git a/contracts/src/tests/test_aggregator_proxy.cairo b/contracts/src/tests/test_aggregator_proxy.cairo index ef33d0f66..2a2c091d4 100644 --- a/contracts/src/tests/test_aggregator_proxy.cairo +++ b/contracts/src/tests/test_aggregator_proxy.cairo @@ -24,6 +24,11 @@ use chainlink::utils::split_felt; use chainlink::tests::test_ownable::should_implement_ownable; use chainlink::tests::test_access_controller::should_implement_access_control; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + + fn STATE() -> AggregatorProxy::ContractState { AggregatorProxy::contract_state_for_testing() } @@ -37,15 +42,22 @@ fn setup() -> ( ) { // Set account as default caller let account: ContractAddress = contract_address_const::<1>(); - set_caller_address(account); + + start_cheat_caller_address_global(account); + // set_caller_address(account); // Deploy mock aggregator 1 let mut calldata = ArrayTrait::new(); calldata.append(8); // decimals = 8 - let (mockAggregatorAddr1, _) = deploy_syscall( - MockAggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let contract_class = declare("MockAggregator").unwrap(); + + let (mockAggregatorAddr1, _) = contract_class.deploy(@calldata).unwrap(); + + // let (mockAggregatorAddr1, _) = deploy_syscall( + // MockAggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let mockAggregator1 = IMockAggregatorDispatcher { contract_address: mockAggregatorAddr1 }; // Deploy mock aggregator 2 @@ -53,10 +65,13 @@ fn setup() -> ( // so we need to change the decimals parameter to avoid an address conflict with mock aggregator 1 let mut calldata2 = ArrayTrait::new(); calldata2.append(10); // decimals = 10 - let (mockAggregatorAddr2, _) = deploy_syscall( - MockAggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata2.span(), false - ) - .unwrap(); + + let (mockAggregatorAddr2, _) = contract_class.deploy(@calldata).unwrap(); + + // let (mockAggregatorAddr2, _) = deploy_syscall( + // MockAggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata2.span(), false + // ) + // .unwrap(); let mockAggregator2 = IMockAggregatorDispatcher { contract_address: mockAggregatorAddr2 }; // Return account, mock aggregator address and mock aggregator contract @@ -69,10 +84,12 @@ fn test_ownable() { // Deploy aggregator proxy let calldata = array![account.into(), // owner = account mockAggregatorAddr.into(),]; - let (aggregatorProxyAddr, _) = deploy_syscall( - AggregatorProxy::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + let (aggregatorProxyAddr, _) = declare("AggregatorProxy").unwrap().deploy(@calldata).unwrap(); + + // let (aggregatorProxyAddr, _) = deploy_syscall( + // AggregatorProxy::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); should_implement_ownable(aggregatorProxyAddr, account); } @@ -83,10 +100,13 @@ fn test_access_control() { // Deploy aggregator proxy let calldata = array![account.into(), // owner = account mockAggregatorAddr.into(),]; - let (aggregatorProxyAddr, _) = deploy_syscall( - AggregatorProxy::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (aggregatorProxyAddr, _) = declare("AggregatorProxy").unwrap().deploy(@calldata).unwrap(); + + // let (aggregatorProxyAddr, _) = deploy_syscall( + // AggregatorProxy::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); should_implement_access_control(aggregatorProxyAddr, account); } @@ -133,7 +153,8 @@ fn test_query_latest_round_data_without_access() { // insert round into mock aggregator mockAggregator.set_latest_round_data(10, 1, 9, 8); // set caller to non-owner address with no read access - set_caller_address(contract_address_const::<2>()); + start_cheat_caller_address_global(contract_address_const::<2>()); + // set_caller_address(contract_address_const::<2>()); // query latest round AggregatorProxyImpl::latest_round_data(@state); } @@ -149,7 +170,8 @@ fn test_query_latest_answer_without_access() { // insert round into mock aggregator mockAggregator.set_latest_round_data(10, 1, 9, 8); // set caller to non-owner address with no read access - set_caller_address(contract_address_const::<2>()); + start_cheat_caller_address_global(contract_address_const::<2>()); + // set_caller_address(contract_address_const::<2>()); // query latest round AggregatorProxyImpl::latest_answer(@state); } diff --git a/contracts/src/tests/test_erc677.cairo b/contracts/src/tests/test_erc677.cairo index 99b9c8fbf..796bec043 100644 --- a/contracts/src/tests/test_erc677.cairo +++ b/contracts/src/tests/test_erc677.cairo @@ -16,6 +16,10 @@ use chainlink::token::mock::invalid_erc667_receiver::InvalidReceiver; use chainlink::libraries::token::erc677::ERC677Component; use chainlink::libraries::token::erc677::ERC677Component::ERC677Impl; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + #[starknet::interface] trait MockInvalidReceiver { fn set_supports(ref self: TContractState, value: bool); @@ -30,16 +34,20 @@ use chainlink::token::mock::valid_erc667_receiver::{ fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<1>(); // Set account as default caller - set_caller_address(account); + start_cheat_caller_address_global(account); + // set_caller_address(account); account } fn setup_valid_receiver() -> (ContractAddress, MockValidReceiverDispatcher) { let calldata = ArrayTrait::new(); - let (address, _) = deploy_syscall( - ValidReceiver::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (address, _) = declare("ValidReceiver").unwrap().deploy(@calldata).unwrap(); + + // let (address, _) = deploy_syscall( + // ValidReceiver::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let contract = MockValidReceiverDispatcher { contract_address: address }; (address, contract) } @@ -47,10 +55,13 @@ fn setup_valid_receiver() -> (ContractAddress, MockValidReceiverDispatcher) { fn setup_invalid_receiver() -> (ContractAddress, MockInvalidReceiverDispatcher) { let calldata = ArrayTrait::new(); - let (address, _) = deploy_syscall( - InvalidReceiver::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (address, _) = declare("InvalidReceiver").unwrap().deploy(@calldata).unwrap(); + + // let (address, _) = deploy_syscall( + // InvalidReceiver::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let contract = MockInvalidReceiverDispatcher { contract_address: address }; (address, contract) } @@ -83,7 +94,7 @@ fn test_valid_transfer_and_call() { } #[test] -#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +#[should_panic] fn test_invalid_receiver_supports_interface_true() { setup(); let (receiver_address, receiver) = setup_invalid_receiver(); @@ -103,7 +114,7 @@ fn test_invalid_receiver_supports_interface_false() { #[test] -#[should_panic(expected: ('CONTRACT_NOT_DEPLOYED',))] +#[should_panic] fn test_nonexistent_receiver() { setup(); diff --git a/contracts/src/tests/test_link_token.cairo b/contracts/src/tests/test_link_token.cairo index 0c0fde3ed..4892ea93b 100644 --- a/contracts/src/tests/test_link_token.cairo +++ b/contracts/src/tests/test_link_token.cairo @@ -17,6 +17,11 @@ use chainlink::token::link_token::LinkToken::{MintableToken, UpgradeableImpl}; use openzeppelin::token::erc20::ERC20Component::{ERC20Impl, ERC20MetadataImpl}; use chainlink::tests::test_ownable::should_implement_ownable; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + + // only tests link token specific functionality // erc20 and erc677 functionality is already tested elsewhere @@ -27,7 +32,8 @@ fn STATE() -> LinkToken::ContractState { fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<1>(); // Set account as default caller - set_caller_address(account); + start_cheat_caller_address_global(account); + // set_caller_address(account); account } @@ -38,10 +44,13 @@ fn test_ownable() { let mut calldata = ArrayTrait::new(); calldata.append(class_hash_const::<123>().into()); // minter calldata.append(account.into()); // owner - let (linkAddr, _) = deploy_syscall( - LinkToken::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (linkAddr, _) = declare("LinkToken").unwrap().deploy(@calldata).unwrap(); + + // let (linkAddr, _) = deploy_syscall( + // LinkToken::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); should_implement_ownable(linkAddr, account); } diff --git a/contracts/src/tests/test_mock_aggregator.cairo b/contracts/src/tests/test_mock_aggregator.cairo index e95b7b87e..81cc8871e 100644 --- a/contracts/src/tests/test_mock_aggregator.cairo +++ b/contracts/src/tests/test_mock_aggregator.cairo @@ -4,6 +4,10 @@ use chainlink::ocr2::mocks::mock_aggregator::MockAggregator; use starknet::contract_address_const; use chainlink::ocr2::aggregator::Round; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + fn STATE() -> MockAggregator::ContractState { MockAggregator::contract_state_for_testing() } @@ -11,7 +15,8 @@ fn STATE() -> MockAggregator::ContractState { fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<777>(); // Set account as default caller - set_caller_address(account); + start_cheat_caller_address_global(account); + // set_caller_address(account); account } diff --git a/contracts/src/tests/test_multisig.cairo b/contracts/src/tests/test_multisig.cairo index becfe4d7d..cd32be7ae 100644 --- a/contracts/src/tests/test_multisig.cairo +++ b/contracts/src/tests/test_multisig.cairo @@ -17,23 +17,10 @@ use chainlink::multisig::Multisig; use chainlink::multisig::Multisig::{MultisigImpl, UpgradeableImpl}; use chainlink::multisig::{IMultisigDispatcher}; -#[starknet::contract] -mod MultisigTest { - use array::ArrayTrait; - - #[storage] - struct Storage {} - - #[abi(per_item)] - #[generate_trait] - impl HelperImpl of HelperTrait { - #[external(v0)] - fn increment(ref self: ContractState, val1: felt252, val2: felt252) -> Array { - array![val1 + 1, val2 + 1] - } - } -} - +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, cheat_caller_address, CheatSpan +}; fn STATE() -> Multisig::ContractState { Multisig::contract_state_for_testing() @@ -123,7 +110,8 @@ fn test_submit_transaction() { let signers = array![signer]; Multisig::constructor(ref state, :signers, threshold: 1); - set_caller_address(signer); + start_cheat_caller_address_global(signer); + // set_caller_address(signer); let to = contract_address_const::<42>(); let function_selector = 10; MultisigImpl::submit_transaction( @@ -147,7 +135,8 @@ fn test_submit_transaction_not_signer() { let signers = array![signer]; Multisig::constructor(ref state, :signers, threshold: 1); - set_caller_address(contract_address_const::<3>()); + start_cheat_caller_address_global(contract_address_const::<3>()); + // set_caller_address(contract_address_const::<3>()); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -164,7 +153,8 @@ fn test_confirm_transaction() { let signers = array![signer1, signer2]; Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -189,15 +179,16 @@ fn test_confirm_transaction_not_signer() { let not_signer = contract_address_const::<2>(); let signers = array![signer]; Multisig::constructor(ref state, :signers, threshold: 1); - set_caller_address(signer); + start_cheat_caller_address_global(signer); + // set_caller_address(signer); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), ); - - set_caller_address(not_signer); + start_cheat_caller_address_global(not_signer); + // set_caller_address(not_signer); MultisigImpl::confirm_transaction(ref state, nonce: 0); } @@ -208,7 +199,8 @@ fn test_revoke_confirmation() { let signer2 = contract_address_const::<2>(); let signers = array![signer1, signer2]; Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -237,7 +229,8 @@ fn test_revoke_confirmation_not_signer() { let not_signer = contract_address_const::<2>(); let mut signers = array![signer]; Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer); + start_cheat_caller_address_global(signer); + // set_caller_address(signer); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -245,8 +238,8 @@ fn test_revoke_confirmation_not_signer() { calldata: sample_calldata(), ); MultisigImpl::confirm_transaction(ref state, nonce: 0); - - set_caller_address(not_signer); + start_cheat_caller_address_global(not_signer); + // set_caller_address(not_signer); MultisigImpl::revoke_confirmation(ref state, nonce: 0); } @@ -257,8 +250,10 @@ fn test_execute_confirmation_below_threshold() { let signer1 = contract_address_const::<1>(); let signer2 = contract_address_const::<2>(); let signers = array![signer1, signer2]; + Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -274,7 +269,8 @@ fn test_execute_confirmation_below_threshold() { fn test_upgrade_not_multisig() { let mut state = STATE(); let account = contract_address_const::<777>(); - set_caller_address(account); + start_cheat_caller_address_global(account); + // set_caller_address(account); UpgradeableImpl::upgrade(ref state, class_hash_const::<1>()) } @@ -286,11 +282,18 @@ fn test_execute() { let signer2 = contract_address_const::<2>(); let signers = array![signer1, signer2]; Multisig::constructor(ref state, :signers, threshold: 2); - let (test_address, _) = deploy_syscall( - MultisigTest::TEST_CLASS_HASH.try_into().unwrap(), 0, ArrayTrait::new().span(), false - ) - .unwrap(); - set_caller_address(signer1); + + let calldata = ArrayTrait::new(); + + let (test_address, _) = declare("MockMultisigTarget").unwrap().deploy(@calldata).unwrap(); + + // let (test_address, _) = deploy_syscall( + // MultisigTest::TEST_CLASS_HASH.try_into().unwrap(), 0, ArrayTrait::new().span(), false + // ) + // .unwrap(); + + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); let increment_calldata = array![42, 100]; MultisigImpl::submit_transaction( ref state, @@ -300,7 +303,8 @@ fn test_execute() { calldata: increment_calldata, ); MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(signer2); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); MultisigImpl::confirm_transaction(ref state, nonce: 0); let response = MultisigImpl::execute_transaction(ref state, nonce: 0); @@ -318,7 +322,8 @@ fn test_execute_not_signer() { let signer2 = contract_address_const::<2>(); let signers = array![signer1, signer2]; Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -326,96 +331,122 @@ fn test_execute_not_signer() { calldata: sample_calldata(), ); MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(signer2); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(contract_address_const::<3>()); + start_cheat_caller_address_global(contract_address_const::<3>()); + // set_caller_address(contract_address_const::<3>()); MultisigImpl::execute_transaction(ref state, nonce: 0); } #[test] #[should_panic(expected: ('transaction invalid',))] fn test_execute_after_set_signers() { - let mut state = STATE(); - let contract_address = contract_address_const::<100>(); - set_contract_address(contract_address); let signer1 = contract_address_const::<1>(); let signer2 = contract_address_const::<2>(); let signer3 = contract_address_const::<3>(); let signers = array![signer1, signer2]; - Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); - MultisigImpl::submit_transaction( - ref state, - to: contract_address_const::<42>(), - function_selector: 10, - calldata: sample_calldata(), - ); - MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(signer2); - MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(contract_address); - let new_signers = array![signer2, signer3]; - MultisigImpl::set_signers(ref state, new_signers); + let init_threshold: usize = 2; - set_caller_address(signer2); - MultisigImpl::execute_transaction(ref state, nonce: 0); + let mut deploy_calldata = ArrayTrait::new(); + Serde::serialize(@signers, ref deploy_calldata); + Serde::serialize(@init_threshold, ref deploy_calldata); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + let multisig = IMultisigDispatcher { contract_address: multisig_address }; + + // Multisig::constructor(ref state, :signers, threshold: 2); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); + multisig + .submit_transaction( + to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), + ); + multisig.confirm_transaction(nonce: 0); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); + multisig.confirm_transaction(nonce: 0); + start_cheat_caller_address_global(multisig_address); + // set_caller_address(contract_address); + let new_signers = array![signer2, signer3]; + multisig.set_signers(new_signers); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); + multisig.execute_transaction(nonce: 0); } #[test] #[should_panic(expected: ('transaction invalid',))] fn test_execute_after_set_signers_and_threshold() { - let mut state = STATE(); - let contract_address = contract_address_const::<100>(); - set_contract_address(contract_address); + // set_contract_address(contract_address); let signer1 = contract_address_const::<1>(); let signer2 = contract_address_const::<2>(); let signer3 = contract_address_const::<3>(); let signers = array![signer1, signer2]; - Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); - MultisigImpl::submit_transaction( - ref state, - to: contract_address_const::<42>(), - function_selector: 10, - calldata: sample_calldata(), - ); - MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(signer2); - MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(contract_address); - let new_signers = array![signer2, signer3]; - MultisigImpl::set_signers_and_threshold(ref state, new_signers, 1); + let init_threshold: usize = 2; - set_caller_address(signer2); - MultisigImpl::execute_transaction(ref state, nonce: 0); + let mut deploy_calldata = ArrayTrait::new(); + Serde::serialize(@signers, ref deploy_calldata); + Serde::serialize(@init_threshold, ref deploy_calldata); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + let multisig = IMultisigDispatcher { contract_address: multisig_address }; + + // Multisig::constructor(ref state, :signers, threshold: 2); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); + multisig + .submit_transaction( + to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), + ); + multisig.confirm_transaction(nonce: 0); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); + multisig.confirm_transaction(nonce: 0); + start_cheat_caller_address_global(multisig_address); + // set_caller_address(contract_address); + let new_signers = array![signer2, signer3]; + multisig.set_signers_and_threshold(new_signers, 1); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); + multisig.execute_transaction(nonce: 0); } #[test] #[should_panic(expected: ('transaction invalid',))] fn test_execute_after_set_threshold() { - let mut state = STATE(); - let contract_address = contract_address_const::<100>(); - set_contract_address(contract_address); let signer1 = contract_address_const::<1>(); let signer2 = contract_address_const::<2>(); let signers = array![signer1, signer2]; - Multisig::constructor(ref state, :signers, threshold: 2); - set_caller_address(signer1); - MultisigImpl::submit_transaction( - ref state, - to: contract_address_const::<42>(), - function_selector: 10, - calldata: sample_calldata(), - ); - MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(signer2); - MultisigImpl::confirm_transaction(ref state, nonce: 0); - set_caller_address(contract_address); - MultisigImpl::set_threshold(ref state, 1); + let init_threshold: usize = 2; - set_caller_address(signer1); - MultisigImpl::execute_transaction(ref state, nonce: 0); + let mut deploy_calldata = ArrayTrait::new(); + Serde::serialize(@signers, ref deploy_calldata); + Serde::serialize(@init_threshold, ref deploy_calldata); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + let multisig = IMultisigDispatcher { contract_address: multisig_address }; + + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); + multisig + .submit_transaction( + to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), + ); + multisig.confirm_transaction(nonce: 0); + start_cheat_caller_address_global(signer2); + // set_caller_address(signer2); + multisig.confirm_transaction(nonce: 0); + start_cheat_caller_address_global(multisig_address); + // set_caller_address(contract_address); + multisig.set_threshold(1); + start_cheat_caller_address_global(signer1); + // set_caller_address(signer1); + multisig.execute_transaction(nonce: 0); } // test set_threshold (non-recursive) @@ -431,14 +462,18 @@ fn test_set_threshold() { let mut deploy_calldata = ArrayTrait::new(); Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = deploy_syscall( - Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - ) - .unwrap(); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + // let (multisig_address, _) = deploy_syscall( + // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false + // ) + // .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; assert(multisig.get_threshold() == init_threshold, 'invalid init threshold'); - set_contract_address(multisig_address); + start_cheat_caller_address_global(multisig_address); + // set_contract_address(multisig_address); multisig.set_threshold(new_threshold); assert(multisig.get_threshold() == new_threshold, 'threshold was not updated'); } @@ -457,10 +492,13 @@ fn test_recursive_set_threshold() { let mut deploy_calldata = ArrayTrait::new(); Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = deploy_syscall( - Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - ) - .unwrap(); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + // let (multisig_address, _) = deploy_syscall( + // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false + // ) + // .unwrap(); // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -472,19 +510,26 @@ fn test_recursive_set_threshold() { // contract. let mut set_threshold_calldata = ArrayTrait::new(); Serde::serialize(@new_threshold, ref set_threshold_calldata); - set_contract_address(s1); + start_cheat_caller_address_global(s1); + // set_contract_address(s1); multisig .submit_transaction(multisig_address, selector!("set_threshold"), set_threshold_calldata); // Signer 1 confirms the transaction - set_contract_address(s1); + start_cheat_caller_address_global(s1); + // set_contract_address(s1); multisig.confirm_transaction(0); // Signer 2 confirms the transaction - set_contract_address(s2); + start_cheat_caller_address_global(s2); + // set_contract_address(s2); multisig.confirm_transaction(0); // Once we have enough confirmations, we execute the transaction - set_contract_address(s1); + // cheat only once because we want the multisig to execute the recursive tx + cheat_caller_address(multisig_address, s1, CheatSpan::TargetCalls(1)); + // start_cheat_caller_address_global(s1); + // set_contract_address(s1); + multisig.execute_transaction(0); // Now we check that the threshold was actually updated @@ -503,10 +548,13 @@ fn test_set_signers() { let mut deploy_calldata = ArrayTrait::new(); Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@threshold, ref deploy_calldata); - let (multisig_address, _) = deploy_syscall( - Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - ) - .unwrap(); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + // let (multisig_address, _) = deploy_syscall( + // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false + // ) + // .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -516,7 +564,8 @@ fn test_set_signers() { assert(*returned_signers.at(1) == s2, 'should match signer 2'); assert(multisig.get_threshold() == 2, 'wrong init threshold'); - set_contract_address(multisig_address); + start_cheat_caller_address_global(multisig_address); + // set_contract_address(multisig_address); multisig.set_signers(new_signers); let updated_signers = multisig.get_signers(); @@ -539,10 +588,13 @@ fn test_recursive_set_signers() { let mut deploy_calldata = ArrayTrait::new(); Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = deploy_syscall( - Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - ) - .unwrap(); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + // let (multisig_address, _) = deploy_syscall( + // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false + // ) + // .unwrap(); // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -559,19 +611,23 @@ fn test_recursive_set_signers() { // contract. let mut set_signers_calldata = ArrayTrait::new(); Serde::serialize(@new_signers, ref set_signers_calldata); - set_contract_address(s1); + start_cheat_caller_address_global(s1); + // set_contract_address(s1); multisig.submit_transaction(multisig_address, selector!("set_signers"), set_signers_calldata); // Signer 1 confirms the transaction - set_contract_address(s1); + start_cheat_caller_address_global(s1); + // set_contract_address(s1); multisig.confirm_transaction(0); // Signer 2 confirms the transaction - set_contract_address(s2); + start_cheat_caller_address_global(s2); + // set_contract_address(s2); multisig.confirm_transaction(0); // Once we have enough confirmations, we execute the transaction - set_contract_address(s1); + cheat_caller_address(multisig_address, s1, CheatSpan::TargetCalls(1)); + // set_contract_address(s1); multisig.execute_transaction(0); // Now we check that the signers were actually updated @@ -595,10 +651,13 @@ fn test_set_signers_and_threshold() { let mut deploy_calldata = ArrayTrait::new(); Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = deploy_syscall( - Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - ) - .unwrap(); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + // let (multisig_address, _) = deploy_syscall( + // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false + // ) + // .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -609,7 +668,8 @@ fn test_set_signers_and_threshold() { assert(*returned_signers.at(2) == s3, 'should match signer 3'); assert(multisig.get_threshold() == init_threshold, 'wrong init threshold'); - set_contract_address(multisig_address); + start_cheat_caller_address_global(multisig_address); + // set_contract_address(multisig_address); multisig.set_signers_and_threshold(new_signers, new_threshold); let updated_signers = multisig.get_signers(); @@ -635,10 +695,13 @@ fn test_recursive_set_signers_and_threshold() { let mut deploy_calldata = ArrayTrait::new(); Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = deploy_syscall( - Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - ) - .unwrap(); + + let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + + // let (multisig_address, _) = deploy_syscall( + // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false + // ) + // .unwrap(); // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -657,7 +720,8 @@ fn test_recursive_set_signers_and_threshold() { let mut set_signers_and_threshold_calldata = ArrayTrait::new(); Serde::serialize(@new_signers, ref set_signers_and_threshold_calldata); Serde::serialize(@new_threshold, ref set_signers_and_threshold_calldata); - set_contract_address(s1); + start_cheat_caller_address_global(s1); + // set_contract_address(s1); multisig .submit_transaction( multisig_address, @@ -666,19 +730,23 @@ fn test_recursive_set_signers_and_threshold() { ); // Signer 1 confirms the transaction - set_contract_address(s1); + start_cheat_caller_address_global(s1); + // set_contract_address(s1); multisig.confirm_transaction(0); // Signer 2 confirms the transaction - set_contract_address(s2); + start_cheat_caller_address_global(s2); + // set_contract_address(s2); multisig.confirm_transaction(0); // Signer 3 confirms the transaction - set_contract_address(s3); + start_cheat_caller_address_global(s3); + // set_contract_address(s3); multisig.confirm_transaction(0); // Once we have enough confirmations, we execute the transaction - set_contract_address(s1); + cheat_caller_address(multisig_address, s1, CheatSpan::TargetCalls(1)); + // set_contract_address(s1); multisig.execute_transaction(0); // Now we check that the signers were actually updated diff --git a/contracts/src/tests/test_ownable.cairo b/contracts/src/tests/test_ownable.cairo index 40e86c1c5..a651f4372 100644 --- a/contracts/src/tests/test_ownable.cairo +++ b/contracts/src/tests/test_ownable.cairo @@ -8,6 +8,10 @@ use openzeppelin::access::ownable::interface::{ IOwnableTwoStep, IOwnableTwoStepDispatcher, IOwnableTwoStepDispatcherTrait }; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + // // General ownable contract tests // @@ -20,13 +24,15 @@ fn should_implement_ownable(contract_addr: ContractAddress, owner: ContractAddre assert(owner == contract.owner(), 'owner does not match'); // transfer ownership - check owner unchanged and proposed owner set correctly - set_contract_address(owner); // required to call contract as owner + start_cheat_caller_address_global(owner); + // set_contract_address(owner); // required to call contract as owner contract.transfer_ownership(acc2); assert(owner == contract.owner(), 'owner should remain unchanged'); assert(acc2 == contract.pending_owner(), 'acc2 should be proposed owner'); // accept ownership - check owner changed and proposed owner set to zero - set_contract_address(acc2); // required to call function as acc2 + start_cheat_caller_address_global(acc2); + // set_contract_address(acc2); // required to call function as acc2 contract.accept_ownership(); assert(contract.owner() == acc2, 'failed to change ownership'); assert(contract.pending_owner().is_zero(), 'proposed owner should be zero'); diff --git a/contracts/src/tests/test_sequencer_uptime_feed.cairo b/contracts/src/tests/test_sequencer_uptime_feed.cairo index 51dabbd25..a3bea486e 100644 --- a/contracts/src/tests/test_sequencer_uptime_feed.cairo +++ b/contracts/src/tests/test_sequencer_uptime_feed.cairo @@ -29,6 +29,11 @@ use chainlink::emergency::sequencer_uptime_feed::{ ISequencerUptimeFeed, ISequencerUptimeFeedDispatcher, ISequencerUptimeFeedDispatcherTrait }; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + + fn PROXY() -> AggregatorProxy::ContractState { AggregatorProxy::contract_state_for_testing() } @@ -39,16 +44,21 @@ fn STATE() -> SequencerUptimeFeed::ContractState { fn setup() -> (ContractAddress, ContractAddress, ISequencerUptimeFeedDispatcher) { let account: ContractAddress = contract_address_const::<777>(); - set_caller_address(account); + + start_cheat_caller_address_global(account); + // set_caller_address(account); // Deploy seqeuencer uptime feed let calldata = array![0, // initial status account.into() // owner ]; - let (sequencerFeedAddr, _) = deploy_syscall( - SequencerUptimeFeed::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (sequencerFeedAddr, _) = declare("SequencerUptimeFeed").unwrap().deploy(@calldata).unwrap(); + + // let (sequencerFeedAddr, _) = deploy_syscall( + // SequencerUptimeFeed::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let sequencerUptimeFeed = ISequencerUptimeFeedDispatcher { contract_address: sequencerFeedAddr }; @@ -72,13 +82,15 @@ fn test_access_control() { #[should_panic()] fn test_set_l1_sender_not_owner() { let (_, _, sequencerUptimeFeed) = setup(); + start_cheat_caller_address_global(contract_address_const::<111>()); sequencerUptimeFeed.set_l1_sender(EthAddress { address: 789 }); } #[test] fn test_set_l1_sender() { let (owner, _, sequencerUptimeFeed) = setup(); - set_contract_address(owner); + start_cheat_caller_address_global(owner); + // set_contract_address(owner); sequencerUptimeFeed.set_l1_sender(EthAddress { address: 789 }); assert(sequencerUptimeFeed.l1_sender().address == 789, 'l1_sender should be set to 789'); } @@ -104,8 +116,8 @@ fn test_latest_answer_no_access() { #[test] fn test_aggregator_proxy_response() { let (owner, sequencerFeedAddr, _) = setup(); - - set_contract_address(owner); + start_cheat_caller_address_global(owner); + // set_contract_address(owner); let contract = IAccessControllerDispatcher { contract_address: sequencerFeedAddr }; contract.add_access(owner); diff --git a/contracts/src/tests/test_upgradeable.cairo b/contracts/src/tests/test_upgradeable.cairo index 4d4213573..1de5e755b 100644 --- a/contracts/src/tests/test_upgradeable.cairo +++ b/contracts/src/tests/test_upgradeable.cairo @@ -16,9 +16,15 @@ use chainlink::libraries::mocks::mock_non_upgradeable::{ IMockNonUpgradeableDispatcherImpl }; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + + fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<777>(); - set_caller_address(account); + start_cheat_caller_address_global(account); + // set_caller_address(account); account } @@ -27,14 +33,19 @@ fn test_upgrade_and_call() { let _ = setup(); let calldata = array![]; - let (contractAddr, _) = deploy_syscall( - MockUpgradeable::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + + let (contractAddr, _) = declare("MockUpgradeable").unwrap().deploy(@calldata).unwrap(); + + // let (contractAddr, _) = deploy_syscall( + // MockUpgradeable::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + // ) + // .unwrap(); let mockUpgradeable = IMockUpgradeableDispatcher { contract_address: contractAddr }; assert(mockUpgradeable.foo() == true, 'should call foo'); - mockUpgradeable.upgrade(MockNonUpgradeable::TEST_CLASS_HASH.try_into().unwrap()); + let contract_class = declare("MockNonUpgradeable").unwrap(); + + mockUpgradeable.upgrade(contract_class.class_hash); // now, contract should be different let mockNonUpgradeable = IMockNonUpgradeableDispatcher { contract_address: contractAddr }; diff --git a/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo index 9826331de..91874ba2e 100644 --- a/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo +++ b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo @@ -1,7 +1,3 @@ -use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global -}; - use chainlink::emergency::sequencer_uptime_feed::ISequencerUptimeFeedDispatcherTrait; use chainlink::emergency::sequencer_uptime_feed::ISequencerUptimeFeedDispatcher; use chainlink::libraries::access_control::IAccessControllerDispatcherTrait; @@ -16,6 +12,10 @@ use starknet::contract_address_const; use starknet::get_caller_address; use starknet::ContractAddress; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(decimals.into()); From 8345c2895c71b7f2e56bcaf5a7b28cc10dc3774f Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 30 Aug 2024 12:35:10 -0400 Subject: [PATCH 10/24] remove dead comments --- .../src/tests/test_access_controller.cairo | 7 -- contracts/src/tests/test_aggregator.cairo | 31 ------- .../src/tests/test_aggregator_proxy.cairo | 21 ----- contracts/src/tests/test_erc677.cairo | 9 -- contracts/src/tests/test_link_token.cairo | 6 -- .../src/tests/test_mock_aggregator.cairo | 1 - contracts/src/tests/test_multisig.cairo | 83 ------------------- contracts/src/tests/test_ownable.cairo | 2 - .../tests/test_sequencer_uptime_feed.cairo | 7 -- contracts/src/tests/test_upgradeable.cairo | 5 -- 10 files changed, 172 deletions(-) diff --git a/contracts/src/tests/test_access_controller.cairo b/contracts/src/tests/test_access_controller.cairo index 5f20b2da2..a78715cf4 100644 --- a/contracts/src/tests/test_access_controller.cairo +++ b/contracts/src/tests/test_access_controller.cairo @@ -31,7 +31,6 @@ fn STATE() -> AccessController::ContractState { fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<777>(); start_cheat_caller_address_global(account); - // set_caller_address(account); account } @@ -53,11 +52,6 @@ fn test_access_control() { let (accessControllerAddr, _) = declare("AccessController").unwrap().deploy(@calldata).unwrap(); - // let (accessControllerAddr, _) = deploy_syscall( - // AccessController::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); - should_implement_access_control(accessControllerAddr, owner); } @@ -72,7 +66,6 @@ fn should_implement_access_control(contract_addr: ContractAddress, owner: Contra let acc2: ContractAddress = contract_address_const::<2222987765>(); start_cheat_caller_address_global(owner); - // set_contract_address(owner); // required to call contract as owner // access check is enabled by default assert(!contract.has_access(acc2, array![]), 'should not have access'); diff --git a/contracts/src/tests/test_aggregator.cairo b/contracts/src/tests/test_aggregator.cairo index cdf12e938..77719d6f9 100644 --- a/contracts/src/tests/test_aggregator.cairo +++ b/contracts/src/tests/test_aggregator.cairo @@ -84,8 +84,6 @@ fn setup() -> ( // set acc1 as default caller start_cheat_caller_address_global(acc1); - // set_caller_address(acc1); - // deploy billing access controller let calldata = array![acc1.into(), // owner = acc1; ]; @@ -95,10 +93,6 @@ fn setup() -> ( .deploy(@calldata) .unwrap(); - // let (billingAccessControllerAddr, _) = deploy_syscall( - // AccessController::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let billingAccessController = IAccessControllerDispatcher { contract_address: billingAccessControllerAddr }; @@ -110,10 +104,6 @@ fn setup() -> ( let (linkTokenAddr, _) = declare("LinkToken").unwrap().deploy(@calldata).unwrap(); - // let (linkTokenAddr, _) = deploy_syscall( - // LinkToken::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let linkToken = ILinkTokenDispatcher { contract_address: linkTokenAddr }; // return accounts, billing access controller, link token @@ -136,11 +126,6 @@ fn test_ownable() { let (aggregatorAddr, _) = declare("Aggregator").unwrap().deploy(@calldata).unwrap(); - // let (aggregatorAddr, _) = deploy_syscall( - // Aggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); - should_implement_ownable(aggregatorAddr, account); } @@ -191,7 +176,6 @@ fn test_set_billing_access_controller_not_owner() { // set billing access controller should revert if caller is not owner start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); BillingImpl::set_billing_access_controller(ref state, billingAccessController.contract_address); } @@ -219,7 +203,6 @@ fn test_set_billing_config_no_access() { gas_per_signature: 1, }; start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); BillingImpl::set_billing(ref state, config); } @@ -261,7 +244,6 @@ fn test_set_billing_config_as_acc_with_access() { let mut state = STATE(); // grant acc2 access on access controller start_cheat_caller_address_global(owner); - // set_contract_address(owner); billingAccessController.add_access(acc2); Aggregator::constructor( @@ -283,7 +265,6 @@ fn test_set_billing_config_as_acc_with_access() { gas_per_signature: 1, }; start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); BillingImpl::set_billing(ref state, config); // check billing config @@ -308,7 +289,6 @@ fn test_set_payees_caller_not_owner() { let payees = array![PayeeConfig { transmitter: acc2, payee: acc2, },]; // set payee should revert if caller is not owner start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); PayeeManagementImpl::set_payees(ref state, payees); } @@ -322,7 +302,6 @@ fn test_set_single_payee() { let payees = array![PayeeConfig { transmitter: acc2, payee: acc2, },]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); } @@ -339,7 +318,6 @@ fn test_set_multiple_payees() { PayeeConfig { transmitter: owner, payee: owner, }, ]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); } @@ -356,7 +334,6 @@ fn test_transfer_payeeship_caller_not_payee() { let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, owner); } @@ -374,10 +351,8 @@ fn test_transfer_payeeship_to_self() { let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, acc2); } @@ -394,10 +369,8 @@ fn test_accept_payeeship_caller_not_proposed_payee() { let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, owner); PayeeManagementImpl::accept_payeeship(ref state, transmitter); } @@ -414,13 +387,10 @@ fn test_transfer_and_accept_payeeship() { let payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); start_cheat_caller_address_global(acc2); - // set_caller_address(acc2); PayeeManagementImpl::transfer_payeeship(ref state, transmitter, owner); start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::accept_payeeship(ref state, transmitter); } // --- Payments and Withdrawals Tests --- @@ -442,7 +412,6 @@ fn test_owed_payment_no_rounds() { let mut payees = array![PayeeConfig { transmitter: transmitter, payee: acc2, },]; start_cheat_caller_address_global(owner); - // set_caller_address(owner); PayeeManagementImpl::set_payees(ref state, payees); let owed = BillingImpl::owed_payment(@state, transmitter); diff --git a/contracts/src/tests/test_aggregator_proxy.cairo b/contracts/src/tests/test_aggregator_proxy.cairo index 2a2c091d4..eb7e350bc 100644 --- a/contracts/src/tests/test_aggregator_proxy.cairo +++ b/contracts/src/tests/test_aggregator_proxy.cairo @@ -44,7 +44,6 @@ fn setup() -> ( let account: ContractAddress = contract_address_const::<1>(); start_cheat_caller_address_global(account); - // set_caller_address(account); // Deploy mock aggregator 1 let mut calldata = ArrayTrait::new(); @@ -54,10 +53,6 @@ fn setup() -> ( let (mockAggregatorAddr1, _) = contract_class.deploy(@calldata).unwrap(); - // let (mockAggregatorAddr1, _) = deploy_syscall( - // MockAggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let mockAggregator1 = IMockAggregatorDispatcher { contract_address: mockAggregatorAddr1 }; // Deploy mock aggregator 2 @@ -68,10 +63,6 @@ fn setup() -> ( let (mockAggregatorAddr2, _) = contract_class.deploy(@calldata).unwrap(); - // let (mockAggregatorAddr2, _) = deploy_syscall( - // MockAggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata2.span(), false - // ) - // .unwrap(); let mockAggregator2 = IMockAggregatorDispatcher { contract_address: mockAggregatorAddr2 }; // Return account, mock aggregator address and mock aggregator contract @@ -86,11 +77,6 @@ fn test_ownable() { mockAggregatorAddr.into(),]; let (aggregatorProxyAddr, _) = declare("AggregatorProxy").unwrap().deploy(@calldata).unwrap(); - // let (aggregatorProxyAddr, _) = deploy_syscall( - // AggregatorProxy::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); - should_implement_ownable(aggregatorProxyAddr, account); } @@ -103,11 +89,6 @@ fn test_access_control() { let (aggregatorProxyAddr, _) = declare("AggregatorProxy").unwrap().deploy(@calldata).unwrap(); - // let (aggregatorProxyAddr, _) = deploy_syscall( - // AggregatorProxy::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); - should_implement_access_control(aggregatorProxyAddr, account); } @@ -154,7 +135,6 @@ fn test_query_latest_round_data_without_access() { mockAggregator.set_latest_round_data(10, 1, 9, 8); // set caller to non-owner address with no read access start_cheat_caller_address_global(contract_address_const::<2>()); - // set_caller_address(contract_address_const::<2>()); // query latest round AggregatorProxyImpl::latest_round_data(@state); } @@ -171,7 +151,6 @@ fn test_query_latest_answer_without_access() { mockAggregator.set_latest_round_data(10, 1, 9, 8); // set caller to non-owner address with no read access start_cheat_caller_address_global(contract_address_const::<2>()); - // set_caller_address(contract_address_const::<2>()); // query latest round AggregatorProxyImpl::latest_answer(@state); } diff --git a/contracts/src/tests/test_erc677.cairo b/contracts/src/tests/test_erc677.cairo index 796bec043..b81394a21 100644 --- a/contracts/src/tests/test_erc677.cairo +++ b/contracts/src/tests/test_erc677.cairo @@ -35,7 +35,6 @@ fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<1>(); // Set account as default caller start_cheat_caller_address_global(account); - // set_caller_address(account); account } @@ -44,10 +43,6 @@ fn setup_valid_receiver() -> (ContractAddress, MockValidReceiverDispatcher) { let (address, _) = declare("ValidReceiver").unwrap().deploy(@calldata).unwrap(); - // let (address, _) = deploy_syscall( - // ValidReceiver::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let contract = MockValidReceiverDispatcher { contract_address: address }; (address, contract) } @@ -58,10 +53,6 @@ fn setup_invalid_receiver() -> (ContractAddress, MockInvalidReceiverDispatcher) let (address, _) = declare("InvalidReceiver").unwrap().deploy(@calldata).unwrap(); - // let (address, _) = deploy_syscall( - // InvalidReceiver::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let contract = MockInvalidReceiverDispatcher { contract_address: address }; (address, contract) } diff --git a/contracts/src/tests/test_link_token.cairo b/contracts/src/tests/test_link_token.cairo index 4892ea93b..1ee0f9946 100644 --- a/contracts/src/tests/test_link_token.cairo +++ b/contracts/src/tests/test_link_token.cairo @@ -33,7 +33,6 @@ fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<1>(); // Set account as default caller start_cheat_caller_address_global(account); - // set_caller_address(account); account } @@ -47,11 +46,6 @@ fn test_ownable() { let (linkAddr, _) = declare("LinkToken").unwrap().deploy(@calldata).unwrap(); - // let (linkAddr, _) = deploy_syscall( - // LinkToken::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); - should_implement_ownable(linkAddr, account); } diff --git a/contracts/src/tests/test_mock_aggregator.cairo b/contracts/src/tests/test_mock_aggregator.cairo index 81cc8871e..af3387415 100644 --- a/contracts/src/tests/test_mock_aggregator.cairo +++ b/contracts/src/tests/test_mock_aggregator.cairo @@ -16,7 +16,6 @@ fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<777>(); // Set account as default caller start_cheat_caller_address_global(account); - // set_caller_address(account); account } diff --git a/contracts/src/tests/test_multisig.cairo b/contracts/src/tests/test_multisig.cairo index cd32be7ae..77eeb17b7 100644 --- a/contracts/src/tests/test_multisig.cairo +++ b/contracts/src/tests/test_multisig.cairo @@ -111,7 +111,6 @@ fn test_submit_transaction() { Multisig::constructor(ref state, :signers, threshold: 1); start_cheat_caller_address_global(signer); - // set_caller_address(signer); let to = contract_address_const::<42>(); let function_selector = 10; MultisigImpl::submit_transaction( @@ -136,7 +135,6 @@ fn test_submit_transaction_not_signer() { Multisig::constructor(ref state, :signers, threshold: 1); start_cheat_caller_address_global(contract_address_const::<3>()); - // set_caller_address(contract_address_const::<3>()); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -154,7 +152,6 @@ fn test_confirm_transaction() { Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -180,7 +177,6 @@ fn test_confirm_transaction_not_signer() { let signers = array![signer]; Multisig::constructor(ref state, :signers, threshold: 1); start_cheat_caller_address_global(signer); - // set_caller_address(signer); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -188,7 +184,6 @@ fn test_confirm_transaction_not_signer() { calldata: sample_calldata(), ); start_cheat_caller_address_global(not_signer); - // set_caller_address(not_signer); MultisigImpl::confirm_transaction(ref state, nonce: 0); } @@ -200,7 +195,6 @@ fn test_revoke_confirmation() { let signers = array![signer1, signer2]; Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -230,7 +224,6 @@ fn test_revoke_confirmation_not_signer() { let mut signers = array![signer]; Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer); - // set_caller_address(signer); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -239,7 +232,6 @@ fn test_revoke_confirmation_not_signer() { ); MultisigImpl::confirm_transaction(ref state, nonce: 0); start_cheat_caller_address_global(not_signer); - // set_caller_address(not_signer); MultisigImpl::revoke_confirmation(ref state, nonce: 0); } @@ -253,7 +245,6 @@ fn test_execute_confirmation_below_threshold() { Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -270,8 +261,6 @@ fn test_upgrade_not_multisig() { let mut state = STATE(); let account = contract_address_const::<777>(); start_cheat_caller_address_global(account); - // set_caller_address(account); - UpgradeableImpl::upgrade(ref state, class_hash_const::<1>()) } @@ -287,13 +276,7 @@ fn test_execute() { let (test_address, _) = declare("MockMultisigTarget").unwrap().deploy(@calldata).unwrap(); - // let (test_address, _) = deploy_syscall( - // MultisigTest::TEST_CLASS_HASH.try_into().unwrap(), 0, ArrayTrait::new().span(), false - // ) - // .unwrap(); - start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); let increment_calldata = array![42, 100]; MultisigImpl::submit_transaction( ref state, @@ -304,7 +287,6 @@ fn test_execute() { ); MultisigImpl::confirm_transaction(ref state, nonce: 0); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); MultisigImpl::confirm_transaction(ref state, nonce: 0); let response = MultisigImpl::execute_transaction(ref state, nonce: 0); @@ -323,7 +305,6 @@ fn test_execute_not_signer() { let signers = array![signer1, signer2]; Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); MultisigImpl::submit_transaction( ref state, to: contract_address_const::<42>(), @@ -332,11 +313,8 @@ fn test_execute_not_signer() { ); MultisigImpl::confirm_transaction(ref state, nonce: 0); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); MultisigImpl::confirm_transaction(ref state, nonce: 0); - start_cheat_caller_address_global(contract_address_const::<3>()); - // set_caller_address(contract_address_const::<3>()); MultisigImpl::execute_transaction(ref state, nonce: 0); } @@ -357,30 +335,24 @@ fn test_execute_after_set_signers() { let multisig = IMultisigDispatcher { contract_address: multisig_address }; - // Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); multisig .submit_transaction( to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), ); multisig.confirm_transaction(nonce: 0); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); multisig.confirm_transaction(nonce: 0); start_cheat_caller_address_global(multisig_address); - // set_caller_address(contract_address); let new_signers = array![signer2, signer3]; multisig.set_signers(new_signers); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); multisig.execute_transaction(nonce: 0); } #[test] #[should_panic(expected: ('transaction invalid',))] fn test_execute_after_set_signers_and_threshold() { - // set_contract_address(contract_address); let signer1 = contract_address_const::<1>(); let signer2 = contract_address_const::<2>(); let signer3 = contract_address_const::<3>(); @@ -397,21 +369,17 @@ fn test_execute_after_set_signers_and_threshold() { // Multisig::constructor(ref state, :signers, threshold: 2); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); multisig .submit_transaction( to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), ); multisig.confirm_transaction(nonce: 0); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); multisig.confirm_transaction(nonce: 0); start_cheat_caller_address_global(multisig_address); - // set_caller_address(contract_address); let new_signers = array![signer2, signer3]; multisig.set_signers_and_threshold(new_signers, 1); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); multisig.execute_transaction(nonce: 0); } @@ -432,20 +400,16 @@ fn test_execute_after_set_threshold() { let multisig = IMultisigDispatcher { contract_address: multisig_address }; start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); multisig .submit_transaction( to: contract_address_const::<42>(), function_selector: 10, calldata: sample_calldata(), ); multisig.confirm_transaction(nonce: 0); start_cheat_caller_address_global(signer2); - // set_caller_address(signer2); multisig.confirm_transaction(nonce: 0); start_cheat_caller_address_global(multisig_address); - // set_caller_address(contract_address); multisig.set_threshold(1); start_cheat_caller_address_global(signer1); - // set_caller_address(signer1); multisig.execute_transaction(nonce: 0); } @@ -465,15 +429,9 @@ fn test_set_threshold() { let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); - // let (multisig_address, _) = deploy_syscall( - // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - // ) - // .unwrap(); - let multisig = IMultisigDispatcher { contract_address: multisig_address }; assert(multisig.get_threshold() == init_threshold, 'invalid init threshold'); start_cheat_caller_address_global(multisig_address); - // set_contract_address(multisig_address); multisig.set_threshold(new_threshold); assert(multisig.get_threshold() == new_threshold, 'threshold was not updated'); } @@ -495,11 +453,6 @@ fn test_recursive_set_threshold() { let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); - // let (multisig_address, _) = deploy_syscall( - // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - // ) - // .unwrap(); - // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -511,24 +464,19 @@ fn test_recursive_set_threshold() { let mut set_threshold_calldata = ArrayTrait::new(); Serde::serialize(@new_threshold, ref set_threshold_calldata); start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig .submit_transaction(multisig_address, selector!("set_threshold"), set_threshold_calldata); // Signer 1 confirms the transaction start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig.confirm_transaction(0); // Signer 2 confirms the transaction start_cheat_caller_address_global(s2); - // set_contract_address(s2); multisig.confirm_transaction(0); // Once we have enough confirmations, we execute the transaction // cheat only once because we want the multisig to execute the recursive tx cheat_caller_address(multisig_address, s1, CheatSpan::TargetCalls(1)); - // start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig.execute_transaction(0); @@ -551,11 +499,6 @@ fn test_set_signers() { let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); - // let (multisig_address, _) = deploy_syscall( - // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - // ) - // .unwrap(); - let multisig = IMultisigDispatcher { contract_address: multisig_address }; let returned_signers = multisig.get_signers(); @@ -565,7 +508,6 @@ fn test_set_signers() { assert(multisig.get_threshold() == 2, 'wrong init threshold'); start_cheat_caller_address_global(multisig_address); - // set_contract_address(multisig_address); multisig.set_signers(new_signers); let updated_signers = multisig.get_signers(); @@ -591,11 +533,6 @@ fn test_recursive_set_signers() { let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); - // let (multisig_address, _) = deploy_syscall( - // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - // ) - // .unwrap(); - // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -612,22 +549,18 @@ fn test_recursive_set_signers() { let mut set_signers_calldata = ArrayTrait::new(); Serde::serialize(@new_signers, ref set_signers_calldata); start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig.submit_transaction(multisig_address, selector!("set_signers"), set_signers_calldata); // Signer 1 confirms the transaction start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig.confirm_transaction(0); // Signer 2 confirms the transaction start_cheat_caller_address_global(s2); - // set_contract_address(s2); multisig.confirm_transaction(0); // Once we have enough confirmations, we execute the transaction cheat_caller_address(multisig_address, s1, CheatSpan::TargetCalls(1)); - // set_contract_address(s1); multisig.execute_transaction(0); // Now we check that the signers were actually updated @@ -654,11 +587,6 @@ fn test_set_signers_and_threshold() { let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); - // let (multisig_address, _) = deploy_syscall( - // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - // ) - // .unwrap(); - let multisig = IMultisigDispatcher { contract_address: multisig_address }; let returned_signers = multisig.get_signers(); @@ -669,7 +597,6 @@ fn test_set_signers_and_threshold() { assert(multisig.get_threshold() == init_threshold, 'wrong init threshold'); start_cheat_caller_address_global(multisig_address); - // set_contract_address(multisig_address); multisig.set_signers_and_threshold(new_signers, new_threshold); let updated_signers = multisig.get_signers(); @@ -698,11 +625,6 @@ fn test_recursive_set_signers_and_threshold() { let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); - // let (multisig_address, _) = deploy_syscall( - // Multisig::TEST_CLASS_HASH.try_into().unwrap(), 0, deploy_calldata.span(), false - // ) - // .unwrap(); - // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -721,7 +643,6 @@ fn test_recursive_set_signers_and_threshold() { Serde::serialize(@new_signers, ref set_signers_and_threshold_calldata); Serde::serialize(@new_threshold, ref set_signers_and_threshold_calldata); start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig .submit_transaction( multisig_address, @@ -731,22 +652,18 @@ fn test_recursive_set_signers_and_threshold() { // Signer 1 confirms the transaction start_cheat_caller_address_global(s1); - // set_contract_address(s1); multisig.confirm_transaction(0); // Signer 2 confirms the transaction start_cheat_caller_address_global(s2); - // set_contract_address(s2); multisig.confirm_transaction(0); // Signer 3 confirms the transaction start_cheat_caller_address_global(s3); - // set_contract_address(s3); multisig.confirm_transaction(0); // Once we have enough confirmations, we execute the transaction cheat_caller_address(multisig_address, s1, CheatSpan::TargetCalls(1)); - // set_contract_address(s1); multisig.execute_transaction(0); // Now we check that the signers were actually updated diff --git a/contracts/src/tests/test_ownable.cairo b/contracts/src/tests/test_ownable.cairo index a651f4372..d898bbb73 100644 --- a/contracts/src/tests/test_ownable.cairo +++ b/contracts/src/tests/test_ownable.cairo @@ -25,14 +25,12 @@ fn should_implement_ownable(contract_addr: ContractAddress, owner: ContractAddre // transfer ownership - check owner unchanged and proposed owner set correctly start_cheat_caller_address_global(owner); - // set_contract_address(owner); // required to call contract as owner contract.transfer_ownership(acc2); assert(owner == contract.owner(), 'owner should remain unchanged'); assert(acc2 == contract.pending_owner(), 'acc2 should be proposed owner'); // accept ownership - check owner changed and proposed owner set to zero start_cheat_caller_address_global(acc2); - // set_contract_address(acc2); // required to call function as acc2 contract.accept_ownership(); assert(contract.owner() == acc2, 'failed to change ownership'); assert(contract.pending_owner().is_zero(), 'proposed owner should be zero'); diff --git a/contracts/src/tests/test_sequencer_uptime_feed.cairo b/contracts/src/tests/test_sequencer_uptime_feed.cairo index a3bea486e..a7b44ff24 100644 --- a/contracts/src/tests/test_sequencer_uptime_feed.cairo +++ b/contracts/src/tests/test_sequencer_uptime_feed.cairo @@ -46,7 +46,6 @@ fn setup() -> (ContractAddress, ContractAddress, ISequencerUptimeFeedDispatcher) let account: ContractAddress = contract_address_const::<777>(); start_cheat_caller_address_global(account); - // set_caller_address(account); // Deploy seqeuencer uptime feed let calldata = array![0, // initial status @@ -55,10 +54,6 @@ fn setup() -> (ContractAddress, ContractAddress, ISequencerUptimeFeedDispatcher) let (sequencerFeedAddr, _) = declare("SequencerUptimeFeed").unwrap().deploy(@calldata).unwrap(); - // let (sequencerFeedAddr, _) = deploy_syscall( - // SequencerUptimeFeed::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let sequencerUptimeFeed = ISequencerUptimeFeedDispatcher { contract_address: sequencerFeedAddr }; @@ -90,7 +85,6 @@ fn test_set_l1_sender_not_owner() { fn test_set_l1_sender() { let (owner, _, sequencerUptimeFeed) = setup(); start_cheat_caller_address_global(owner); - // set_contract_address(owner); sequencerUptimeFeed.set_l1_sender(EthAddress { address: 789 }); assert(sequencerUptimeFeed.l1_sender().address == 789, 'l1_sender should be set to 789'); } @@ -117,7 +111,6 @@ fn test_latest_answer_no_access() { fn test_aggregator_proxy_response() { let (owner, sequencerFeedAddr, _) = setup(); start_cheat_caller_address_global(owner); - // set_contract_address(owner); let contract = IAccessControllerDispatcher { contract_address: sequencerFeedAddr }; contract.add_access(owner); diff --git a/contracts/src/tests/test_upgradeable.cairo b/contracts/src/tests/test_upgradeable.cairo index 1de5e755b..fee927616 100644 --- a/contracts/src/tests/test_upgradeable.cairo +++ b/contracts/src/tests/test_upgradeable.cairo @@ -24,7 +24,6 @@ use snforge_std::{ fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<777>(); start_cheat_caller_address_global(account); - // set_caller_address(account); account } @@ -36,10 +35,6 @@ fn test_upgrade_and_call() { let (contractAddr, _) = declare("MockUpgradeable").unwrap().deploy(@calldata).unwrap(); - // let (contractAddr, _) = deploy_syscall( - // MockUpgradeable::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); let mockUpgradeable = IMockUpgradeableDispatcher { contract_address: contractAddr }; assert(mockUpgradeable.foo() == true, 'should call foo'); From c8e67f0355ff944938560ce2d7bc4a6f0633b97c Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 30 Aug 2024 17:15:31 -0400 Subject: [PATCH 11/24] write some set_config tests --- contracts/Scarb.toml | 2 +- contracts/src/mcms.cairo | 20 ++ contracts/src/tests.cairo | 1 + contracts/src/tests/test_mcms.cairo | 508 +++++++++++++++++++++++++++- 4 files changed, 523 insertions(+), 8 deletions(-) diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 261bc54b7..a84f65f83 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -7,7 +7,7 @@ homepage = "https://github.com/smartcontractkit/chainlink-starknet" [scripts] sierra = "cairo-compile . -r" -# test = "snforge test" +test = "snforge test" # Add your own custom commands and run them with scarb run # Uncomment if you want to use dependencies diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 3f3e8d613..c49c07d91 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -138,10 +138,18 @@ mod ManyChainMultiSig { call_contract_syscall }; use starknet::eth_signature::is_eth_signature_valid; + + use openzeppelin::access::ownable::OwnableComponent; + use alexandria_bytes::{Bytes, BytesTrait}; use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + const NUM_GROUPS: u8 = 32; const MAX_NUM_SIGNERS: u8 = 200; @@ -154,6 +162,8 @@ mod ManyChainMultiSig { #[storage] struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, // s_signers is used to easily validate the existence of the signer by its address. s_signers: LegacyMap, // begin s_config (defined in storage bc Config struct cannot support maps) @@ -195,11 +205,19 @@ mod ManyChainMultiSig { #[event] #[derive(Drop, starknet::Event)] enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, NewRoot: NewRoot, OpExecuted: OpExecuted, ConfigSet: ConfigSet, } + #[constructor] + fn constructor(ref self: ContractState) { + let caller = starknet::info::get_caller_address(); + self.ownable.initializer(caller); + } + #[abi(embed_v0)] impl ManyChainMultiSigImpl of super::IManyChainMultiSig { @@ -390,6 +408,8 @@ mod ManyChainMultiSig { group_parents: Span, clear_root: bool ) { + self.ownable.assert_only_owner(); + assert( signer_addresses.len() != 0 && signer_addresses.len() <= MAX_NUM_SIGNERS.into(), 'out of bound signers len' diff --git a/contracts/src/tests.cairo b/contracts/src/tests.cairo index 4d8104499..3ee396c65 100644 --- a/contracts/src/tests.cairo +++ b/contracts/src/tests.cairo @@ -10,3 +10,4 @@ mod test_upgradeable; mod test_access_controller; mod test_mock_aggregator; mod test_sequencer_uptime_feed; +mod test_mcms; diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index 7233889b2..4290138c1 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -1,15 +1,509 @@ +use core::array::{SpanTrait, ArrayTrait}; +use starknet::{ContractAddress, EthAddress, EthAddressZeroable}; +use chainlink::mcms::{ + ManyChainMultiSig, IManyChainMultiSigDispatcher, IManyChainMultiSigSafeDispatcher, + IManyChainMultiSigSafeDispatcherTrait, ManyChainMultiSig::{MAX_NUM_SIGNERS} +}; + +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global +}; + // 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 -// 5. test if one of signer_group #'s is out of bounds NUM_GROUPS -// 6. test if group_parents[i] is greater than or equal to i (when not 0) there is revert -// 7. test if i is 0 and group_parents[i] != 0 and revert -// 8. test if there is a signer in a group where group_quorum[i] == 0 => revert -// 9. test if there are not enough signers to meet a quorum => revert -// 10. test if signer addresses are not in ascending order -// 11. successful => test without clearing root. test the state of storage variables and that event was emitted + +// 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 } + ) +} + +#[test] +#[feature("safe_dispatcher")] +fn test_set_config_out_of_bound_signers() { + // 1. test if len(signer_address) = 0 => revert + let (_, _, mcms_safe) = setup(); + + let signer_addresses = array![]; + let signer_groups = array![]; + let group_quorums = array![]; + let group_parents = array![]; + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'out of bound signers len'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'out of bound signers len', *panic_data.at(0)); + } + } + + // 2. test if lena(signer_address) > MAX_NUM_SIGNERS => revert + + // todo: use fixed-size array in cairo >= 2.7.0 + // let signer_addresses = [EthAddressZeroable::zero(); 201]; + + let mut signer_addresses = ArrayTrait::new(); + let mut i = 0; + while i < 201_usize { + signer_addresses.append(EthAddressZeroable::zero()); + i += 1; + }; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'out of bound signers len'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'out of bound signers len', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![]; + let group_quorums = array![]; + let group_parents = array![]; + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'signer groups len mismatch'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer groups len mismatch', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + let group_quorums = array![0]; + let group_parents = array![0]; + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); + } + } + + // 5. test if group_quorum and group_parents not equal in length + + // todo: replace with [0_u8; 32] in cairo 2.7.0 + let mut group_quorums = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + group_quorums.append(0); + i += 1; + }; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![33]; + + let mut group_quorums = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + group_quorums.append(0); + i += 1; + }; + + let mut group_parents = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + group_parents.append(0); + i += 1; + }; + + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'out of bounds group'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'out of bounds group', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + + let mut group_quorums = 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 mut 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, + 31 + ]; + + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group tree malformed'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group tree malformed', *panic_data.at(0)); + } + } + + let mut group_parents = array![ + 1, + 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group tree malformed'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group tree malformed', *panic_data.at(0)); + } + } +} + +#[test] +#[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 mut signer_addresses = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + signer_addresses.append(EthAddressZeroable::zero()); + i += 1; + }; + let signer_groups = 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 mut group_quorums = 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 mut 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'signer in disabled group'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer in disabled group', *panic_data.at(0)); + } + } +} +// 10. test if there are not enough signers to meet a quorum => revert From 191b0dfebf3a9d106fec89d3096426dd4de99218 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 30 Aug 2024 17:20:01 -0400 Subject: [PATCH 12/24] reduce signers arr --- contracts/src/tests/test_mcms.cairo | 43 ++--------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index 4290138c1..01eeb2840 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -378,46 +378,8 @@ 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 mut signer_addresses = ArrayTrait::new(); - let mut i = 0; - while i < 32_usize { - signer_addresses.append(EthAddressZeroable::zero()); - i += 1; - }; - let signer_groups = 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 mut signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; let mut group_quorums = array![ 0, 0, @@ -506,4 +468,3 @@ fn test_set_config_signer_in_disabled_group() { } // 10. test if there are not enough signers to meet a quorum => revert - From 618317930ddf9be8df1bb1e7e88c6ebc9a68cdf6 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Fri, 30 Aug 2024 17:28:19 -0400 Subject: [PATCH 13/24] finish err cases for set_config --- contracts/src/mcms.cairo | 2 +- contracts/src/tests/test_mcms.cairo | 193 ++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index c49c07d91..1baf28180 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -499,7 +499,7 @@ mod ManyChainMultiSig { let signer_address = *signer_addresses.at(i.into()); assert( to_u256(prev_signer_address) < to_u256(signer_address), - 'addresses not sorted' + 'signer addresses not sorted' ); let signer = Signer { diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index 01eeb2840..d9c0a3952 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -25,6 +25,7 @@ use snforge_std::{ // 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 @@ -466,5 +467,197 @@ fn test_set_config_signer_in_disabled_group() { } } } + // 10. test if there are not enough signers to meet a quorum => revert +#[test] +#[feature("safe_dispatcher")] +fn test_set_config_quorum_impossible() { + let (_, _, mcms_safe) = setup(); + + let mut signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + let mut 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 mut 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'quorum impossible'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'quorum impossible', *panic_data.at(0)); + } + } +} + +// 11. test if signer addresses are not in ascending order +#[test] +#[feature("safe_dispatcher")] +fn test_set_config_signer_addresses_not_sorted() { + let (_, _, mcms_safe) = setup(); + + let mut signer_addresses: Array = array![ + // 0x1 address + u256 { high: 0, low: 1 }.into(), EthAddressZeroable::zero() + ]; + let signer_groups = array![0, 0]; + let mut 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 mut 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'signer addresses not sorted'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer addresses not sorted', *panic_data.at(0)); + } + } +} From a9eb98277a56709f230290ab7678d3c0ac029b49 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 3 Sep 2024 14:12:09 -0400 Subject: [PATCH 14/24] finish set config tests --- contracts/src/mcms.cairo | 21 +- contracts/src/tests/test_mcms.cairo | 368 +++++++++++++++++++++++++++- 2 files changed, 379 insertions(+), 10 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 1baf28180..0d5f79684 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -38,14 +38,14 @@ trait IManyChainMultiSig { fn get_root_metadata(self: @TContractState) -> RootMetadata; } -#[derive(Copy, Drop, Serde, starknet::Store)] +#[derive(Copy, Drop, Serde, starknet::Store, PartialEq, Debug)] struct Signer { address: EthAddress, index: u8, group: u8 } -#[derive(Copy, Drop, Serde, starknet::Store)] +#[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] struct RootMetadata { chain_id: u256, multisig: ContractAddress, @@ -67,14 +67,14 @@ struct Op { } // does not implement Storage trait because structs cannot support arrays or maps -#[derive(Copy, Drop, Serde)] +#[derive(Copy, Drop, Serde, PartialEq)] struct Config { signers: Span, group_quorums: Span, group_parents: Span } -#[derive(Copy, Drop, Serde, starknet::Store)] +#[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] struct ExpiringRootAndOpCount { root: u256, valid_until: u32, @@ -249,11 +249,11 @@ mod ManyChainMultiSig { }; assert( - to_u256(prev_address) < to_u256(signer_address), + to_u256(prev_address) < to_u256(signer_address.clone()), 'signer address must increase' ); - let signer = self.s_signers.read(signer_address); + let signer = self.get_signer_by_address(signer_address); assert(signer.address == signer_address, 'invalid signer'); let mut group = signer.group; @@ -478,6 +478,7 @@ mod ManyChainMultiSig { self.s_signers.write(old_signer.address, empty_signer); // reset _s_config_signers self._s_config_signers.write(i.into(), empty_signer); + i += 1; }; // reset _s_config_signers_len self._s_config_signers_len.write(0); @@ -515,6 +516,9 @@ mod ManyChainMultiSig { i += 1; }; + // length will always be less than MAX_NUM_SIGNERS so try_into will never panic + self._s_config_signers_len.write(signer_addresses.len().try_into().unwrap()); + if clear_root { let op_count = self.s_expiring_root_and_op_count.read().op_count; self @@ -566,6 +570,7 @@ mod ManyChainMultiSig { while i < NUM_GROUPS { group_quorums.append(self._s_config_group_quorums.read(i)); group_parents.append(self._s_config_group_parents.read(i)); + i += 1; }; Config { @@ -596,6 +601,10 @@ mod ManyChainMultiSig { ) { let _response = call_contract_syscall(target, selector, data).unwrap_syscall(); } + + fn get_signer_by_address(ref self: ContractState, signer_address: EthAddress) -> Signer { + self.s_signers.read(signer_address) + } } } diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index d9c0a3952..a59d2f976 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -1,12 +1,23 @@ use core::array::{SpanTrait, ArrayTrait}; -use starknet::{ContractAddress, EthAddress, EthAddressZeroable}; +use starknet::{ContractAddress, EthAddress, EthAddressZeroable, contract_address_const}; use chainlink::mcms::{ - ManyChainMultiSig, IManyChainMultiSigDispatcher, IManyChainMultiSigSafeDispatcher, - IManyChainMultiSigSafeDispatcherTrait, ManyChainMultiSig::{MAX_NUM_SIGNERS} + ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, + ManyChainMultiSig::{ + 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, stop_cheat_caller_address_global + 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 }; // set_config tests @@ -661,3 +672,352 @@ fn test_set_config_signer_addresses_not_sorted() { } } +// test success, root not cleared, event emitted +// 12. successful => test without clearing root. test the state of storage variables and that event was emitted +// +// ┌──────┐ +// ┌─►│2-of-2│ +// │ └──────┘ +// │ ▲ +// │ │ +// ┌──┴───┐ ┌──┴───┐ +// signer 1 signer 2 +// └──────┘ └──────┘ +#[test] +fn test_set_config_success_dont_clear_root() { + let (mcms_address, mcms, _) = setup(); + + let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); + let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); + 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 expected_signer_1 = Signer { address: signer_address_1, index: 0, group: 0 }; + let expected_signer_2 = Signer { address: signer_address_2, index: 1, group: 0 }; + + let expected_config = Config { + signers: array![expected_signer_1, expected_signer_2].span(), + group_quorums: group_quorums.span(), + group_parents: group_parents.span(), + }; + + spy + .assert_emitted( + @array![ + ( + mcms_address, + ManyChainMultiSig::Event::ConfigSet( + ManyChainMultiSig::ConfigSet { + config: expected_config, is_root_cleared: false + } + ) + ) + ] + ); + let config = mcms.get_config(); + // assert(config.signers == expected_config.signers, 'signers not equal'); + // assert(config.group_quorums == expected_config.group_quorums, 'group quorums not equal'); + // assert(config.group_parents == expected_config.group_parents, 'group parents not equal'); + // test the members + assert(config == expected_config, 'config should be same'); + + // mock the contract state + let test_address = test_address(); + start_cheat_caller_address(test_address, contract_address_const::<777>()); + + // test internal function state + let mut state = contract_state_for_testing(); + ManyChainMultiSig::constructor(ref state); + state + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + 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'); + + // test second set_config + let new_signer_address_1: EthAddress = u256 { high: 0, low: 3 }.into(); + let new_signer_address_2: EthAddress = u256 { high: 0, low: 4 }.into(); + let new_signer_addresses = array![new_signer_address_1, new_signer_address_2]; + + mcms + .set_config( + new_signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + let new_config = mcms.get_config(); + + let new_expected_signer_1 = Signer { address: new_signer_address_1, index: 0, group: 0 }; + let new_expected_signer_2 = Signer { address: new_signer_address_2, index: 1, group: 0 }; + + let new_expected_config = Config { + signers: array![new_expected_signer_1, new_expected_signer_2].span(), + group_quorums: group_quorums.span(), + group_parents: group_parents.span(), + }; + + assert(new_config == new_expected_config, 'new config should be same'); + + state + .set_config( + new_signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + 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'); + + // test old signers were reset + let old_signer_1 = state.get_signer_by_address(signer_address_1); + let old_signer_2 = state.get_signer_by_address(signer_address_2); + assert(old_signer_1.address == EthAddressZeroable::zero(), 'old signer 1 should be reset'); + assert(old_signer_2.address == EthAddressZeroable::zero(), 'old signer 1 should be reset'); +} + + +// test that the config was reset +#[test] +fn test_set_config_success_and_clear_root() { + // mock the contract state + let test_address = test_address(); + let mock_chain_id = 990; + start_cheat_caller_address(test_address, contract_address_const::<777>()); + start_cheat_chain_id(test_address, mock_chain_id); + + let mut state = contract_state_for_testing(); + ManyChainMultiSig::constructor(ref state); + + // initialize s_expiring_root_and_op_count & s_root_metadata + state + .s_expiring_root_and_op_count + .write( + ExpiringRootAndOpCount { + root: u256 { high: 777, low: 777 }, valid_until: 102934894, op_count: 134 + } + ); + + state + .s_root_metadata + .write( + RootMetadata { + chain_id: 123123, + multisig: contract_address_const::<111>(), + pre_op_count: 20, + post_op_count: 155, + override_previous_root: false + } + ); + + let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); + let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); + 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 = true; + + state + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + let expected_s_expiring_root_and_op_count = ExpiringRootAndOpCount { + root: u256 { high: 0, low: 0 }, valid_until: 0, op_count: 134 + }; + let s_expiring_root_and_op_count = state.s_expiring_root_and_op_count.read(); + assert!( + s_expiring_root_and_op_count == expected_s_expiring_root_and_op_count, + "s_expiring_root_and_op_count not equal" + ); + + let expected_s_root_metadata = RootMetadata { + chain_id: mock_chain_id.into(), + multisig: test_address, + pre_op_count: 134, + post_op_count: 134, + override_previous_root: true + }; + let s_root_metadata = state.s_root_metadata.read(); + assert(expected_s_root_metadata == s_root_metadata, 's_root_metadata not equal'); +} + From 33b24885b79f3e456e060e005838feade8128624 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Thu, 5 Sep 2024 15:19:38 -0400 Subject: [PATCH 15/24] split out mcms tests --- contracts/src/tests/test_mcms.cairo | 1024 +---------------- .../src/tests/test_mcms/test_set_config.cairo | 1023 ++++++++++++++++ 2 files changed, 1024 insertions(+), 1023 deletions(-) create mode 100644 contracts/src/tests/test_mcms/test_set_config.cairo diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index a59d2f976..dcddd73bb 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -1,1023 +1 @@ -use core::array::{SpanTrait, ArrayTrait}; -use starknet::{ContractAddress, EthAddress, EthAddressZeroable, contract_address_const}; -use chainlink::mcms::{ - ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, - ManyChainMultiSig::{ - 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 -}; - -// 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 } - ) -} - -#[test] -#[feature("safe_dispatcher")] -fn test_set_config_out_of_bound_signers() { - // 1. test if len(signer_address) = 0 => revert - let (_, _, mcms_safe) = setup(); - - let signer_addresses = array![]; - let signer_groups = array![]; - let group_quorums = array![]; - let group_parents = array![]; - let clear_root = false; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'out of bound signers len'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'out of bound signers len', *panic_data.at(0)); - } - } - - // 2. test if lena(signer_address) > MAX_NUM_SIGNERS => revert - - // todo: use fixed-size array in cairo >= 2.7.0 - // let signer_addresses = [EthAddressZeroable::zero(); 201]; - - let mut signer_addresses = ArrayTrait::new(); - let mut i = 0; - while i < 201_usize { - signer_addresses.append(EthAddressZeroable::zero()); - i += 1; - }; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'out of bound signers len'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'out of bound signers len', *panic_data.at(0)); - } - } -} - -#[test] -#[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 signer_addresses = array![EthAddressZeroable::zero()]; - let signer_groups = array![]; - let group_quorums = array![]; - let group_parents = array![]; - let clear_root = false; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'signer groups len mismatch'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'signer groups len mismatch', *panic_data.at(0)); - } - } -} - -#[test] -#[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 signer_addresses = array![EthAddressZeroable::zero()]; - let signer_groups = array![0]; - let group_quorums = array![0]; - let group_parents = array![0]; - let clear_root = false; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); - } - } - - // 5. test if group_quorum and group_parents not equal in length - - // todo: replace with [0_u8; 32] in cairo 2.7.0 - let mut group_quorums = ArrayTrait::new(); - let mut i = 0; - while i < 32_usize { - group_quorums.append(0); - i += 1; - }; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); - } - } -} - -#[test] -#[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 signer_addresses = array![EthAddressZeroable::zero()]; - let signer_groups = array![33]; - - let mut group_quorums = ArrayTrait::new(); - let mut i = 0; - while i < 32_usize { - group_quorums.append(0); - i += 1; - }; - - let mut group_parents = ArrayTrait::new(); - let mut i = 0; - while i < 32_usize { - group_parents.append(0); - i += 1; - }; - - let clear_root = false; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'out of bounds group'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'out of bounds group', *panic_data.at(0)); - } - } -} - -#[test] -#[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 signer_addresses = array![EthAddressZeroable::zero()]; - let signer_groups = array![0]; - - let mut group_quorums = 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 mut 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, - 31 - ]; - - let clear_root = false; - - let result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'group tree malformed'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'group tree malformed', *panic_data.at(0)); - } - } - - let mut group_parents = array![ - 1, - 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 result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'group tree malformed'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'group tree malformed', *panic_data.at(0)); - } - } -} - -#[test] -#[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 mut signer_addresses = array![EthAddressZeroable::zero()]; - let signer_groups = array![0]; - let mut group_quorums = 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 mut 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 result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'signer in disabled group'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'signer in disabled group', *panic_data.at(0)); - } - } -} - -// 10. test if there are not enough signers to meet a quorum => revert -#[test] -#[feature("safe_dispatcher")] -fn test_set_config_quorum_impossible() { - let (_, _, mcms_safe) = setup(); - - let mut signer_addresses = array![EthAddressZeroable::zero()]; - let signer_groups = array![0]; - let mut 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 mut 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 result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'quorum impossible'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'quorum impossible', *panic_data.at(0)); - } - } -} - -// 11. test if signer addresses are not in ascending order -#[test] -#[feature("safe_dispatcher")] -fn test_set_config_signer_addresses_not_sorted() { - let (_, _, mcms_safe) = setup(); - - let mut signer_addresses: Array = array![ - // 0x1 address - u256 { high: 0, low: 1 }.into(), EthAddressZeroable::zero() - ]; - let signer_groups = array![0, 0]; - let mut 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 mut 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 result = mcms_safe - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - match result { - Result::Ok(_) => panic!("expect 'signer addresses not sorted'"), - Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'signer addresses not sorted', *panic_data.at(0)); - } - } -} - -// test success, root not cleared, event emitted -// 12. successful => test without clearing root. test the state of storage variables and that event was emitted -// -// ┌──────┐ -// ┌─►│2-of-2│ -// │ └──────┘ -// │ ▲ -// │ │ -// ┌──┴───┐ ┌──┴───┐ -// signer 1 signer 2 -// └──────┘ └──────┘ -#[test] -fn test_set_config_success_dont_clear_root() { - let (mcms_address, mcms, _) = setup(); - - let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); - let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); - 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 expected_signer_1 = Signer { address: signer_address_1, index: 0, group: 0 }; - let expected_signer_2 = Signer { address: signer_address_2, index: 1, group: 0 }; - - let expected_config = Config { - signers: array![expected_signer_1, expected_signer_2].span(), - group_quorums: group_quorums.span(), - group_parents: group_parents.span(), - }; - - spy - .assert_emitted( - @array![ - ( - mcms_address, - ManyChainMultiSig::Event::ConfigSet( - ManyChainMultiSig::ConfigSet { - config: expected_config, is_root_cleared: false - } - ) - ) - ] - ); - let config = mcms.get_config(); - // assert(config.signers == expected_config.signers, 'signers not equal'); - // assert(config.group_quorums == expected_config.group_quorums, 'group quorums not equal'); - // assert(config.group_parents == expected_config.group_parents, 'group parents not equal'); - // test the members - assert(config == expected_config, 'config should be same'); - - // mock the contract state - let test_address = test_address(); - start_cheat_caller_address(test_address, contract_address_const::<777>()); - - // test internal function state - let mut state = contract_state_for_testing(); - ManyChainMultiSig::constructor(ref state); - state - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - 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'); - - // test second set_config - let new_signer_address_1: EthAddress = u256 { high: 0, low: 3 }.into(); - let new_signer_address_2: EthAddress = u256 { high: 0, low: 4 }.into(); - let new_signer_addresses = array![new_signer_address_1, new_signer_address_2]; - - mcms - .set_config( - new_signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - let new_config = mcms.get_config(); - - let new_expected_signer_1 = Signer { address: new_signer_address_1, index: 0, group: 0 }; - let new_expected_signer_2 = Signer { address: new_signer_address_2, index: 1, group: 0 }; - - let new_expected_config = Config { - signers: array![new_expected_signer_1, new_expected_signer_2].span(), - group_quorums: group_quorums.span(), - group_parents: group_parents.span(), - }; - - assert(new_config == new_expected_config, 'new config should be same'); - - state - .set_config( - new_signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - 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'); - - // test old signers were reset - let old_signer_1 = state.get_signer_by_address(signer_address_1); - let old_signer_2 = state.get_signer_by_address(signer_address_2); - assert(old_signer_1.address == EthAddressZeroable::zero(), 'old signer 1 should be reset'); - assert(old_signer_2.address == EthAddressZeroable::zero(), 'old signer 1 should be reset'); -} - - -// test that the config was reset -#[test] -fn test_set_config_success_and_clear_root() { - // mock the contract state - let test_address = test_address(); - let mock_chain_id = 990; - start_cheat_caller_address(test_address, contract_address_const::<777>()); - start_cheat_chain_id(test_address, mock_chain_id); - - let mut state = contract_state_for_testing(); - ManyChainMultiSig::constructor(ref state); - - // initialize s_expiring_root_and_op_count & s_root_metadata - state - .s_expiring_root_and_op_count - .write( - ExpiringRootAndOpCount { - root: u256 { high: 777, low: 777 }, valid_until: 102934894, op_count: 134 - } - ); - - state - .s_root_metadata - .write( - RootMetadata { - chain_id: 123123, - multisig: contract_address_const::<111>(), - pre_op_count: 20, - post_op_count: 155, - override_previous_root: false - } - ); - - let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); - let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); - 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 = true; - - state - .set_config( - signer_addresses.span(), - signer_groups.span(), - group_quorums.span(), - group_parents.span(), - clear_root - ); - - let expected_s_expiring_root_and_op_count = ExpiringRootAndOpCount { - root: u256 { high: 0, low: 0 }, valid_until: 0, op_count: 134 - }; - let s_expiring_root_and_op_count = state.s_expiring_root_and_op_count.read(); - assert!( - s_expiring_root_and_op_count == expected_s_expiring_root_and_op_count, - "s_expiring_root_and_op_count not equal" - ); - - let expected_s_root_metadata = RootMetadata { - chain_id: mock_chain_id.into(), - multisig: test_address, - pre_op_count: 134, - post_op_count: 134, - override_previous_root: true - }; - let s_root_metadata = state.s_root_metadata.read(); - assert(expected_s_root_metadata == s_root_metadata, 's_root_metadata not equal'); -} - +mod test_set_config; diff --git a/contracts/src/tests/test_mcms/test_set_config.cairo b/contracts/src/tests/test_mcms/test_set_config.cairo new file mode 100644 index 000000000..a59d2f976 --- /dev/null +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -0,0 +1,1023 @@ +use core::array::{SpanTrait, ArrayTrait}; +use starknet::{ContractAddress, EthAddress, EthAddressZeroable, contract_address_const}; +use chainlink::mcms::{ + ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, + ManyChainMultiSig::{ + 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 +}; + +// 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 } + ) +} + +#[test] +#[feature("safe_dispatcher")] +fn test_set_config_out_of_bound_signers() { + // 1. test if len(signer_address) = 0 => revert + let (_, _, mcms_safe) = setup(); + + let signer_addresses = array![]; + let signer_groups = array![]; + let group_quorums = array![]; + let group_parents = array![]; + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'out of bound signers len'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'out of bound signers len', *panic_data.at(0)); + } + } + + // 2. test if lena(signer_address) > MAX_NUM_SIGNERS => revert + + // todo: use fixed-size array in cairo >= 2.7.0 + // let signer_addresses = [EthAddressZeroable::zero(); 201]; + + let mut signer_addresses = ArrayTrait::new(); + let mut i = 0; + while i < 201_usize { + signer_addresses.append(EthAddressZeroable::zero()); + i += 1; + }; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'out of bound signers len'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'out of bound signers len', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![]; + let group_quorums = array![]; + let group_parents = array![]; + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'signer groups len mismatch'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer groups len mismatch', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + let group_quorums = array![0]; + let group_parents = array![0]; + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); + } + } + + // 5. test if group_quorum and group_parents not equal in length + + // todo: replace with [0_u8; 32] in cairo 2.7.0 + let mut group_quorums = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + group_quorums.append(0); + i += 1; + }; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![33]; + + let mut group_quorums = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + group_quorums.append(0); + i += 1; + }; + + let mut group_parents = ArrayTrait::new(); + let mut i = 0; + while i < 32_usize { + group_parents.append(0); + i += 1; + }; + + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'out of bounds group'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'out of bounds group', *panic_data.at(0)); + } + } +} + +#[test] +#[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 signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + + let mut group_quorums = 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 mut 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, + 31 + ]; + + let clear_root = false; + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group tree malformed'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group tree malformed', *panic_data.at(0)); + } + } + + let mut group_parents = array![ + 1, + 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'group tree malformed'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'group tree malformed', *panic_data.at(0)); + } + } +} + +#[test] +#[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 mut signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + let mut group_quorums = 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 mut 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'signer in disabled group'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer in disabled group', *panic_data.at(0)); + } + } +} + +// 10. test if there are not enough signers to meet a quorum => revert +#[test] +#[feature("safe_dispatcher")] +fn test_set_config_quorum_impossible() { + let (_, _, mcms_safe) = setup(); + + let mut signer_addresses = array![EthAddressZeroable::zero()]; + let signer_groups = array![0]; + let mut 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 mut 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'quorum impossible'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'quorum impossible', *panic_data.at(0)); + } + } +} + +// 11. test if signer addresses are not in ascending order +#[test] +#[feature("safe_dispatcher")] +fn test_set_config_signer_addresses_not_sorted() { + let (_, _, mcms_safe) = setup(); + + let mut signer_addresses: Array = array![ + // 0x1 address + u256 { high: 0, low: 1 }.into(), EthAddressZeroable::zero() + ]; + let signer_groups = array![0, 0]; + let mut 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 mut 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 result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'signer addresses not sorted'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer addresses not sorted', *panic_data.at(0)); + } + } +} + +// test success, root not cleared, event emitted +// 12. successful => test without clearing root. test the state of storage variables and that event was emitted +// +// ┌──────┐ +// ┌─►│2-of-2│ +// │ └──────┘ +// │ ▲ +// │ │ +// ┌──┴───┐ ┌──┴───┐ +// signer 1 signer 2 +// └──────┘ └──────┘ +#[test] +fn test_set_config_success_dont_clear_root() { + let (mcms_address, mcms, _) = setup(); + + let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); + let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); + 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 expected_signer_1 = Signer { address: signer_address_1, index: 0, group: 0 }; + let expected_signer_2 = Signer { address: signer_address_2, index: 1, group: 0 }; + + let expected_config = Config { + signers: array![expected_signer_1, expected_signer_2].span(), + group_quorums: group_quorums.span(), + group_parents: group_parents.span(), + }; + + spy + .assert_emitted( + @array![ + ( + mcms_address, + ManyChainMultiSig::Event::ConfigSet( + ManyChainMultiSig::ConfigSet { + config: expected_config, is_root_cleared: false + } + ) + ) + ] + ); + let config = mcms.get_config(); + // assert(config.signers == expected_config.signers, 'signers not equal'); + // assert(config.group_quorums == expected_config.group_quorums, 'group quorums not equal'); + // assert(config.group_parents == expected_config.group_parents, 'group parents not equal'); + // test the members + assert(config == expected_config, 'config should be same'); + + // mock the contract state + let test_address = test_address(); + start_cheat_caller_address(test_address, contract_address_const::<777>()); + + // test internal function state + let mut state = contract_state_for_testing(); + ManyChainMultiSig::constructor(ref state); + state + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + 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'); + + // test second set_config + let new_signer_address_1: EthAddress = u256 { high: 0, low: 3 }.into(); + let new_signer_address_2: EthAddress = u256 { high: 0, low: 4 }.into(); + let new_signer_addresses = array![new_signer_address_1, new_signer_address_2]; + + mcms + .set_config( + new_signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + let new_config = mcms.get_config(); + + let new_expected_signer_1 = Signer { address: new_signer_address_1, index: 0, group: 0 }; + let new_expected_signer_2 = Signer { address: new_signer_address_2, index: 1, group: 0 }; + + let new_expected_config = Config { + signers: array![new_expected_signer_1, new_expected_signer_2].span(), + group_quorums: group_quorums.span(), + group_parents: group_parents.span(), + }; + + assert(new_config == new_expected_config, 'new config should be same'); + + state + .set_config( + new_signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + 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'); + + // test old signers were reset + let old_signer_1 = state.get_signer_by_address(signer_address_1); + let old_signer_2 = state.get_signer_by_address(signer_address_2); + assert(old_signer_1.address == EthAddressZeroable::zero(), 'old signer 1 should be reset'); + assert(old_signer_2.address == EthAddressZeroable::zero(), 'old signer 1 should be reset'); +} + + +// test that the config was reset +#[test] +fn test_set_config_success_and_clear_root() { + // mock the contract state + let test_address = test_address(); + let mock_chain_id = 990; + start_cheat_caller_address(test_address, contract_address_const::<777>()); + start_cheat_chain_id(test_address, mock_chain_id); + + let mut state = contract_state_for_testing(); + ManyChainMultiSig::constructor(ref state); + + // initialize s_expiring_root_and_op_count & s_root_metadata + state + .s_expiring_root_and_op_count + .write( + ExpiringRootAndOpCount { + root: u256 { high: 777, low: 777 }, valid_until: 102934894, op_count: 134 + } + ); + + state + .s_root_metadata + .write( + RootMetadata { + chain_id: 123123, + multisig: contract_address_const::<111>(), + pre_op_count: 20, + post_op_count: 155, + override_previous_root: false + } + ); + + let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); + let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); + 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 = true; + + state + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + let expected_s_expiring_root_and_op_count = ExpiringRootAndOpCount { + root: u256 { high: 0, low: 0 }, valid_until: 0, op_count: 134 + }; + let s_expiring_root_and_op_count = state.s_expiring_root_and_op_count.read(); + assert!( + s_expiring_root_and_op_count == expected_s_expiring_root_and_op_count, + "s_expiring_root_and_op_count not equal" + ); + + let expected_s_root_metadata = RootMetadata { + chain_id: mock_chain_id.into(), + multisig: test_address, + pre_op_count: 134, + post_op_count: 134, + override_previous_root: true + }; + let s_root_metadata = state.s_root_metadata.read(); + assert(expected_s_root_metadata == s_root_metadata, 's_root_metadata not equal'); +} + From 32dc464d060167ce0cd20f0c05b0feffb3a18545 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Wed, 11 Sep 2024 12:29:30 -0400 Subject: [PATCH 16/24] fix unit tests --- contracts/Scarb.lock | 1 + contracts/Scarb.toml | 3 + contracts/package.json | 3 +- .../mocks/mock_multisig_target.cairo | 15 +- contracts/src/mcms.cairo | 131 ++-- contracts/src/tests/test_aggregator.cairo | 5 - contracts/src/tests/test_mcms.cairo | 3 + .../src/tests/test_mcms/test_set_config.cairo | 89 ++- .../src/tests/test_mcms/test_set_root.cairo | 563 ++++++++++++++++++ contracts/src/tests/test_mcms/utils.cairo | 157 +++++ contracts/src/utils.cairo | 1 - contracts/test/mcms-generate-signature.ts | 59 ++ tsconfig.json | 3 + 13 files changed, 972 insertions(+), 61 deletions(-) create mode 100644 contracts/src/tests/test_mcms/test_set_root.cairo create mode 100644 contracts/src/tests/test_mcms/utils.cairo create mode 100644 contracts/test/mcms-generate-signature.ts diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 0c0ba3523..900ef28e5 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -59,6 +59,7 @@ version = "0.1.0" dependencies = [ "alexandria_bytes", "alexandria_encoding", + "alexandria_math", "openzeppelin", "snforge_std", ] diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index a84f65f83..97e8ee5d4 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -19,6 +19,9 @@ alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria. alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" } +[dev-dependencies] +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } + [lib] [[target.starknet-contract]] diff --git a/contracts/package.json b/contracts/package.json index b8a109c9e..03460f8f9 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "compile:solidity": "hardhat compile", - "test": "hardhat test" + "test": "hardhat test", + "generate:signature": "ts-node ./mcms-generate-signature" }, "author": "", "license": "MIT", diff --git a/contracts/src/libraries/mocks/mock_multisig_target.cairo b/contracts/src/libraries/mocks/mock_multisig_target.cairo index 686fa976d..ca6fd450f 100644 --- a/contracts/src/libraries/mocks/mock_multisig_target.cairo +++ b/contracts/src/libraries/mocks/mock_multisig_target.cairo @@ -3,7 +3,10 @@ mod MockMultisigTarget { use array::ArrayTrait; #[storage] - struct Storage {} + struct Storage { + value: felt252, + toggle: bool + } #[abi(per_item)] #[generate_trait] @@ -12,5 +15,15 @@ mod MockMultisigTarget { 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()); + } } } diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 0d5f79684..620ad217c 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -122,6 +122,89 @@ fn hash_pair(a: u256, b: u256) -> u256 { BytesTrait::new_empty().encode(lower).encode(higher).keccak() } +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() +} + +// keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP") +const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP: u256 = + 0x08d275622006c4ca82d03f498e90163cafd53c663a48470c3b52ac8bfbd9f52c; +// keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA") +const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = + 0xe6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c; + +// todo: make sure this is the right way to encode the struct +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() +} + +fn eip_191_message_hash(msg: u256) -> u256 { + let mut eip_191_msg: Bytes = BytesTrait::new_empty(); + + // '\x19Ethereum Signed Message:\n32' in byte array + let prefix = array![ + 0x19, + 0x45, + 0x74, + 0x68, + 0x65, + 0x72, + 0x65, + 0x75, + 0x6d, + 0x20, + 0x53, + 0x69, + 0x67, + 0x6e, + 0x65, + 0x64, + 0x20, + 0x4d, + 0x65, + 0x73, + 0x73, + 0x61, + 0x67, + 0x65, + 0x3a, + 0x0a, + 0x33, + 0x32 + ]; + + let mut i = 0; + while i < prefix.len() { + eip_191_msg.append_u8(*prefix.at(i)); + i += 1; + }; + eip_191_msg.append_u256(msg); + + eip_191_msg.keccak() +} + #[starknet::contract] mod ManyChainMultiSig { use core::array::ArrayTrait; @@ -131,13 +214,12 @@ mod ManyChainMultiSig { use core::traits::PanicDestruct; use super::{ ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op, Signature, recover_eth_ecdsa, - to_u256, verify_merkle_proof + to_u256, verify_merkle_proof, hash_op, hash_metadata, eip_191_message_hash }; use starknet::{ EthAddress, EthAddressZeroable, EthAddressIntoFelt252, ContractAddress, call_contract_syscall }; - use starknet::eth_signature::is_eth_signature_valid; use openzeppelin::access::ownable::OwnableComponent; @@ -232,14 +314,11 @@ mod ManyChainMultiSig { ) { let encoded_root: Bytes = BytesTrait::new_empty().encode(root).encode(valid_until); - let mut eip_191_msg: Bytes = BytesTrait::new_empty(); - eip_191_msg.append_felt252('\x19Ethereum Signed Message:\n32'); - eip_191_msg.append_u256(encoded_root.keccak()); - let msg_hash = eip_191_msg.keccak(); + let msg_hash = eip_191_message_hash(encoded_root.keccak()); assert(!self.s_seen_signed_hashes.read(msg_hash), 'signed hash already seen'); - let prev_address = EthAddressZeroable::zero(); + let mut prev_address = EthAddressZeroable::zero(); let mut group_vote_counts: Felt252Dict = Default::default(); while let Option::Some(signature) = signatures .pop_front() { @@ -252,6 +331,7 @@ mod ManyChainMultiSig { to_u256(prev_address) < to_u256(signer_address.clone()), 'signer address must increase' ); + prev_address = signer_address; let signer = self.get_signer_by_address(signer_address); assert(signer.address == signer_address, 'invalid signer'); @@ -280,20 +360,12 @@ mod ManyChainMultiSig { valid_until.into() >= starknet::info::get_block_timestamp(), 'valid until has passed' ); + // verify metadataProof - // todo: make sure this is the right way to encode the struct - 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); - - let hashed_leaf = encoded_metadata.keccak(); + let hashed_metadata_leaf = hash_metadata(metadata, valid_until); assert( - verify_merkle_proof(metadata_proof, root, hashed_leaf), 'proof verification failed' + verify_merkle_proof(metadata_proof, root, hashed_metadata_leaf), + 'proof verification failed' ); // maybe move to beginning of function @@ -308,10 +380,12 @@ mod ManyChainMultiSig { let op_count = self.s_expiring_root_and_op_count.read().op_count; let current_root_metadata = self.s_root_metadata.read(); + + // new root can be set only if the current op_count is the expected post op count (unless an override is requested) assert( op_count == current_root_metadata.post_op_count || current_root_metadata.override_previous_root, - 'expect pending operations' + 'pending operations remain' ); assert(op_count == metadata.pre_op_count, 'wrong pre-operation count'); assert(metadata.pre_op_count <= metadata.post_op_count, 'wrong post-operation count'); @@ -362,23 +436,10 @@ mod ManyChainMultiSig { assert(op.nonce == current_expiring_root_and_op_count.op_count, 'wrong nonce'); // verify op exists in merkle tree - 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; - }; - let hashed_leaf = encoded_leaf.keccak(); + let hashed_op_leaf = hash_op(op); assert( - verify_merkle_proof(proof, current_expiring_root_and_op_count.root, hashed_leaf), + verify_merkle_proof(proof, current_expiring_root_and_op_count.root, hashed_op_leaf), 'proof verification failed' ); diff --git a/contracts/src/tests/test_aggregator.cairo b/contracts/src/tests/test_aggregator.cairo index 77719d6f9..eecca9a88 100644 --- a/contracts/src/tests/test_aggregator.cairo +++ b/contracts/src/tests/test_aggregator.cairo @@ -145,11 +145,6 @@ fn test_access_control() { let (aggregatorAddr, _) = declare("Aggregator").unwrap().deploy(@calldata).unwrap(); - // let (aggregatorAddr, _) = deploy_syscall( - // Aggregator::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - // ) - // .unwrap(); - should_implement_access_control(aggregatorAddr, account); } diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index dcddd73bb..44e9ff10f 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -1 +1,4 @@ mod test_set_config; +mod test_set_root; +mod utils; + diff --git a/contracts/src/tests/test_mcms/test_set_config.cairo b/contracts/src/tests/test_mcms/test_set_config.cairo index a59d2f976..5ece62f25 100644 --- a/contracts/src/tests/test_mcms/test_set_config.cairo +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -1,5 +1,8 @@ use core::array::{SpanTrait, ArrayTrait}; -use starknet::{ContractAddress, EthAddress, EthAddressZeroable, contract_address_const}; +use starknet::{ + ContractAddress, EthAddress, Felt252TryIntoEthAddress, EthAddressIntoFelt252, + EthAddressZeroable, contract_address_const +}; use chainlink::mcms::{ ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, ManyChainMultiSig::{ @@ -17,7 +20,8 @@ use snforge_std::{ 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, + cheatcodes::{events::{EventSpy}} }; // set_config tests @@ -672,23 +676,25 @@ fn test_set_config_signer_addresses_not_sorted() { } } -// test success, root not cleared, event emitted -// 12. successful => test without clearing root. test the state of storage variables and that event was emitted -// -// ┌──────┐ -// ┌─►│2-of-2│ -// │ └──────┘ -// │ ▲ -// │ │ -// ┌──┴───┐ ┌──┴───┐ -// signer 1 signer 2 -// └──────┘ └──────┘ -#[test] -fn test_set_config_success_dont_clear_root() { - let (mcms_address, mcms, _) = setup(); - let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); - let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); +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_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); + // let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); let signer_addresses: Array = array![signer_address_1, signer_address_2]; let signer_groups = array![0, 0]; let group_quorums = array![ @@ -772,6 +778,53 @@ fn test_set_config_success_dont_clear_root() { 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 +// +// ┌──────┐ +// ┌─►│2-of-2│ +// │ └──────┘ +// │ ▲ +// │ │ +// ┌──┴───┐ ┌──┴───┐ +// signer 1 signer 2 +// └──────┘ └──────┘ +#[test] +fn test_set_config_success_dont_clear_root() { + let signer_address_1: EthAddress = (0x141).try_into().unwrap(); + let signer_address_2: EthAddress = (0x2412).try_into().unwrap(); + let ( + mut spy, + mcms_address, + mcms, + _, + _, + signer_addresses, + signer_groups, + group_quorums, + group_parents, + clear_root + ) = + setup_2_of_2_mcms_no_root( + signer_address_1, signer_address_2 + ); + let expected_signer_1 = Signer { address: signer_address_1, index: 0, group: 0 }; let expected_signer_2 = Signer { address: signer_address_2, index: 1, group: 0 }; diff --git a/contracts/src/tests/test_mcms/test_set_root.cairo b/contracts/src/tests/test_mcms/test_set_root.cairo new file mode 100644 index 000000000..b6281ffd7 --- /dev/null +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -0,0 +1,563 @@ +use alexandria_data_structures::array_ext::ArrayTraitExt; +use alexandria_bytes::{Bytes, BytesTrait}; +use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; +use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; +use core::array::{SpanTrait, ArrayTrait}; +use starknet::{ + eth_signature::is_eth_signature_valid, ContractAddress, EthAddress, EthAddressIntoFelt252, + EthAddressZeroable, contract_address_const, eth_signature::public_key_point_to_eth_address, + secp256_trait::{ + Secp256Trait, Secp256PointTrait, recover_public_key, is_signature_entry_valid, Signature, + signature_from_vrs + }, + secp256k1::Secp256k1Point, SyscallResult, SyscallResultTrait +}; +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 + }, + IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, + IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, + ManyChainMultiSig::{MAX_NUM_SIGNERS}, +}; +use chainlink::tests::test_mcms::test_set_config::setup_2_of_2_mcms_no_root; +use chainlink::tests::test_mcms::utils::{insecure_sign}; + +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, + cheatcodes::{events::{EventSpy}}, start_cheat_block_timestamp_global +}; + +// 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 + +// metadata should be the last leaf +// will only work with an even number of ops +fn merkle_root(leafs: Array) -> (u256, Span) { + let mut level: Array = ArrayTrait::new(); + let odd = leafs.len() % 2 == 1; + let mut metadata: Option = Option::None; + + if odd { + metadata = Option::Some(*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(); + + // 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 = match metadata { + Option::Some(m) => hash_pair(*level.at(0), m), + Option::None => (*level.at(0)), + }; + + (root, array![metadata_proof].span()) +} + +// sets up root +fn setup_2_of_2_mcms() -> ( + 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 signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) + .try_into() + .unwrap(); + 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( + 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(); + + // mock chain id & timestamp + let mock_chain_id = 732; + start_cheat_chain_id_global(mock_chain_id); + + start_cheat_block_timestamp_global(3); + + // 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 + // todo update + 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); + + // abi.encode(root, valid_until) + // output: `sign this msg hash: 80900408832728789228986457074438699885219690940027717206734875544162448258660`` + println!("sign this msg hash: {:?}", encoded_root.keccak()); + + // run `yarn ts-node test/mcms-generate-signature.ts + + // signature 1: + // r: + // high: 0x547a06aecfe75c9bca6d20fb0571ca37, low: 0x1ba523139c3c00145f74f997b224c0ae + // s: + // high: 0x00696f026d9371e3009588d81391174f, low: 0xb85561559edf82cbebf145a8d77681a9 + // v: + // 27 + // signature 2: + // r: + // high: 0x73b0625d2623d18af2cbc023511386c7, low: 0x9d04510e392300e7a69dea2b89d83dd6 + // s: + // high: 0x55ce476d61b1cecdad20854760996cc7, low: 0x12754fc6e8ac845779e3d22d5a443957 + // v: + // 28 + + let signature1 = signature_from_vrs( + v: 27, + r: u256 { + high: 0x547a06aecfe75c9bca6d20fb0571ca37, low: 0x1ba523139c3c00145f74f997b224c0ae, + }, + s: u256 { + high: 0x00696f026d9371e3009588d81391174f, low: 0xb85561559edf82cbebf145a8d77681a9 + }, + ); + + let signature2 = signature_from_vrs( + v: 28, + r: u256 { + high: 0x73b0625d2623d18af2cbc023511386c7, low: 0x9d04510e392300e7a69dea2b89d83dd6, + }, + s: u256 { + high: 0x55ce476d61b1cecdad20854760996cc7, low: 0x12754fc6e8ac845779e3d22d5a443957 + }, + ); + + let signatures = array![signature1, signature2]; + + let ops = array![op1.clone(), op2.clone()]; + + ( + 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() -> ( + 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( + 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(); + + // mock chain id & timestamp + let mock_chain_id = 732; + start_cheat_chain_id_global(mock_chain_id); + + start_cheat_block_timestamp_global(3); + + // 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 + // todo update + 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 (r_1, s_1, y_parity_1) = insecure_sign(message_hash, private_key_1); + let (r_2, s_2, y_parity_2) = insecure_sign(message_hash, private_key_2); + + let signature1 = Signature { r: r_1, s: s_1, y_parity: y_parity_1 }; + let signature2 = Signature { r: r_2, s: s_2, y_parity: y_parity_2 }; + + let addr1 = recover_eth_ecdsa(message_hash, signature1).unwrap(); + let addr2 = recover_eth_ecdsa(message_hash, signature2).unwrap(); + + assert(addr1 == signer_address_1, 'signer 1 not equal'); + assert(addr2 == signer_address_2, 'signer 2 not equal'); + + let signatures = array![signature1, signature2]; + + let ops = array![op1.clone(), op2.clone()]; + + ( + 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 + ) +} + +#[test] +fn test_set_root_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); + + let (actual_root, actual_valid_until) = mcms.get_root(); + + assert(actual_root == root, 'root returned not equal'); + assert(actual_valid_until == valid_until, 'valid until not equal'); + + spy + .assert_emitted( + @array![ + ( + mcms_address, + ManyChainMultiSig::Event::NewRoot( + ManyChainMultiSig::NewRoot { + root: root, valid_until: valid_until, metadata: metadata + } + ) + ) + ] + ); +} +#[test] +#[feature("safe_dispatcher")] +fn test_set_root_hash_seen() { + 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.clone()); + + let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + match result { + Result::Ok(_) => panic!("expect 'signed hash already seen'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signed hash already seen', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_set_root_signatures_wrong_order() { + 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(); + + let unsorted_signatures = array![*signatures.at(1), *signatures.at(0)]; + + let result = safe_mcms + .set_root(root, valid_until, metadata, metadata_proof, unsorted_signatures); + + match result { + Result::Ok(_) => panic!("expect 'signer address must increase'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'signer address must increase', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_set_root_signatures_invalid_signer() { + 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(); + + let invalid_signatures = array![ + signature_from_vrs( + v: 27, + r: u256 { + high: 0x9e8df5d64fb9d2ae155b435ac37519fd, low: 0x6d1ffddf225cde953f6c97f8b3a7531d, + }, + s: u256 { + high: 0x21f13cc6eb1d14f6ebdc497411c57589, low: 0xea109b402fcde2cfe8f3d1b6d2bb8948 + }, + ), + signature_from_vrs( + v: 27, + r: u256 { + high: 0x7a5d64ca9b1814e15eb8df73b3c79ac2, low: 0x9b9080ac6546e07b1118b16e5651e19d, + }, + s: u256 { + high: 0x62794369d5bb5f5a02d2eb6805951990, low: 0xdfcd8563639dcc6668e235e1bea93303 + }, + ) + ]; + + let result = safe_mcms + .set_root(root, valid_until, metadata, metadata_proof, invalid_signatures); + + match result { + Result::Ok(_) => panic!("expect 'invalid signer'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'invalid signer', *panic_data.at(0)); + } + } +} + + diff --git a/contracts/src/tests/test_mcms/utils.cairo b/contracts/src/tests/test_mcms/utils.cairo new file mode 100644 index 000000000..517c7b92f --- /dev/null +++ b/contracts/src/tests/test_mcms/utils.cairo @@ -0,0 +1,157 @@ +use core::integer::{u512, u256_wide_mul}; +use alexandria_bytes::{Bytes, BytesTrait}; +use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; +use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; +use alexandria_math::u512_arithmetics; +use core::math::{u256_mul_mod_n, u256_div_mod_n}; +use core::zeroable::{IsZeroResult, NonZero, zero_based}; +use alexandria_math::u512_arithmetics::{u512_add, u512_sub, U512Intou256X2,}; + +use starknet::{ + ContractAddress, EthAddress, EthAddressIntoFelt252, EthAddressZeroable, contract_address_const, + eth_signature::public_key_point_to_eth_address, + secp256_trait::{ + Secp256Trait, Secp256PointTrait, recover_public_key, is_signature_entry_valid, Signature, + signature_from_vrs + }, + secp256k1::{Secp256k1Point, Secp256k1Impl}, SyscallResult, SyscallResultTrait +}; + +use chainlink::mcms::{ + ExpiringRootAndOpCount, RootMetadata, Config, Signer, 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, + s_root_metadataContractMemberStateTrait + }, + IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, + IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, + ManyChainMultiSig::{MAX_NUM_SIGNERS}, +}; + +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() +} + +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() +} + +// 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 + } + } +} + + +impl U512PartialOrd of PartialOrd { + #[inline(always)] + fn le(lhs: u512, rhs: u512) -> bool { + !(rhs < lhs) + } + #[inline(always)] + fn ge(lhs: u512, rhs: u512) -> bool { + !(lhs < rhs) + } + fn lt(lhs: u512, rhs: u512) -> bool { + if lhs.limb3 < rhs.limb3 { + true + } else if lhs.limb3 == rhs.limb3 { + if lhs.limb2 < rhs.limb2 { + true + } else if lhs.limb2 == rhs.limb2 { + if lhs.limb1 < rhs.limb1 { + true + } else { + false + } + } else { + false + } + } else { + false + } + } + #[inline(always)] + fn gt(lhs: u512, rhs: u512) -> bool { + rhs < lhs + } +} + +// *** THIS IS CRYPTOGRAPHICALLY INSECURE *** +// the usage of a constant random target means that anyone can reverse engineer the private keys +// therefore this method is only meant to be used for tests +// arg z: message hash, arg e: private key +fn insecure_sign(z: u256, e: u256) -> (u256, u256, bool) { + let z_u512: u512 = u256_wide_mul(z, (0x1).into()); + + // order of the finite group + let N = Secp256k1Impl::get_curve_size().try_into().unwrap(); + let n_u512: u512 = u256_wide_mul(N, (0x1).into()); + + // "random" number k would be generated by a pseudo-random number generator + // in secure applications it's important that k is random, or else the private key can + // be derived from r and s + let k = 777; + + // random target + let R = Secp256k1Impl::get_generator_point().mul(k).unwrap(); + let (r_x, r_y) = R.get_coordinates().unwrap(); + + // calculate s = ( z + r*e ) / k (finite element operations) + // where product = r*e and sum = z + r*re + let product = u256_mul_mod_n(r_x, e, N.try_into().unwrap()); + let product_u512: u512 = u256_wide_mul(product, (0x1).into()); + + // sum = z + product (finite element operations) + // avoid u256 overflow by casting to u512 + let mut sum_u512 = u512_add(z_u512, product_u512); + while sum_u512 >= n_u512 { + sum_u512 = u512_sub(sum_u512, n_u512); + }; + let sum = sum_u512.try_into().unwrap(); + + let s = u256_div_mod_n(sum, k, N.try_into().unwrap()).unwrap(); + + let v = 27 + (r_y % 2); + + let y_parity = v % 2 == 0; + + (r_x, s, y_parity) +} + diff --git a/contracts/src/utils.cairo b/contracts/src/utils.cairo index 13db189c9..248917648 100644 --- a/contracts/src/utils.cairo +++ b/contracts/src/utils.cairo @@ -7,4 +7,3 @@ fn split_felt(felt: felt252) -> (u128, u128) { U128sFromFelt252Result::Wide((high, low)) => (high, low), } } - diff --git a/contracts/test/mcms-generate-signature.ts b/contracts/test/mcms-generate-signature.ts new file mode 100644 index 000000000..e55958afb --- /dev/null +++ b/contracts/test/mcms-generate-signature.ts @@ -0,0 +1,59 @@ +import { Wallet, utils } from 'ethers' + +const SIGNER_ADDRESS_1 = '0x13Cf92228941e27eBce80634Eba36F992eCB148A' +const SIGNER_PRIVATE_KEY_1 = '0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688' +const SIGNER_ADDRESS_2 = '0xDa09C953823E1F60916E85faD44bF99A7DACa267' +const SIGNER_PRIVATE_KEY_2 = '0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527' + +const toBytes = (message: string): Uint8Array => { + const numBytes = message.length / 2 + const bytes = new Uint8Array(numBytes) + + for (let i = 0; i < numBytes; i++) { + const byteValue = parseInt(message.slice(2 * i, 2 * i + 2), 16) + bytes[i] = byteValue + } + + return bytes +} + +const generateSignature = async (merkleRoot: string) => { + const signer1 = new Wallet(new utils.SigningKey(SIGNER_PRIVATE_KEY_1)) + const signer2 = new Wallet(new utils.SigningKey(SIGNER_PRIVATE_KEY_2)) + + const signers = [signer1, signer2] + + const merkleRootHex = BigInt(merkleRoot).toString(16) + + const msgToSign = toBytes(merkleRootHex) + + for (let i = 0; i < signers.length; i++) { + const signature = utils.splitSignature(await signers[i].signMessage(msgToSign)) + + const highR = '0x' + signature.r.slice(2, 34); // First 32 characters (high u128) + const lowR = '0x' + signature.r.slice(34); // Last 32 characters (low u128) + + const highS = '0x' + signature.s.slice(2, 34); // First 32 characters (high u128) + const lowS = '0x' + signature.s.slice(34); // Last 32 characters (low u128) + + console.log(` + signature ${i + 1}: + r: + high: ${highR}, low: ${lowR} + s: + high: ${highS}, low: ${lowS} + v: + ${signature.v} + `) + } +} + + +// run scarb test -e chainlink::tests::test_mcms::test_set_root::test_set_root_success and observe standard out to get the merkle root +// then execute `yarn ts-node test/mcms-generate-signature.ts ` +const main = async () => { + + await generateSignature(process.argv[2]) +} + +main() diff --git a/tsconfig.json b/tsconfig.json index 0c5132c51..a92ff1c2f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,9 @@ "files": [], "include": [], "references": [ + { + "path": "./contracts" + }, { "path": "./packages-ts/starknet-gauntlet" }, From a0bb8738ed60a1df3a685beac37316adbeed3ff7 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Mon, 16 Sep 2024 16:56:10 -0400 Subject: [PATCH 17/24] finish set root tests --- contracts/src/mcms.cairo | 26 +- .../src/tests/test_mcms/test_set_config.cairo | 24 +- .../src/tests/test_mcms/test_set_root.cairo | 621 +++++++++++++++--- 3 files changed, 546 insertions(+), 125 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 620ad217c..b3cc50238 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -214,7 +214,8 @@ mod ManyChainMultiSig { use core::traits::PanicDestruct; use super::{ ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op, Signature, recover_eth_ecdsa, - to_u256, verify_merkle_proof, hash_op, hash_metadata, eip_191_message_hash + to_u256, verify_merkle_proof, hash_op, hash_metadata, eip_191_message_hash, + MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP, MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA }; use starknet::{ EthAddress, EthAddressZeroable, EthAddressIntoFelt252, ContractAddress, @@ -233,14 +234,7 @@ mod ManyChainMultiSig { impl OwnableInternalImpl = OwnableComponent::InternalImpl; const NUM_GROUPS: u8 = 32; - const MAX_NUM_SIGNERS: u8 = 200; - // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP") - const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP: u256 = - 0x08d275622006c4ca82d03f498e90163cafd53c663a48470c3b52ac8bfbd9f52c; - // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA") - const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = - 0xe6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c; #[storage] struct Storage { @@ -338,8 +332,6 @@ mod ManyChainMultiSig { let mut group = signer.group; loop { - // todo: may be unnecessary assert - assert(group < NUM_GROUPS, 'invalid group number'); let counts = group_vote_counts.get(group.into()); group_vote_counts.insert(group.into(), counts + 1); if counts + 1 != self._s_config_group_quorums.read(group) { @@ -354,7 +346,7 @@ mod ManyChainMultiSig { }; let root_group_quorum = self._s_config_group_quorums.read(0); - assert(root_group_quorum > 0, 'missing config'); + assert(root_group_quorum > 0, 'root group missing quorum'); assert(group_vote_counts.get(0) >= root_group_quorum, 'insufficient signers'); assert( valid_until.into() >= starknet::info::get_block_timestamp(), @@ -398,7 +390,8 @@ 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( Event::NewRoot( @@ -460,7 +453,7 @@ mod ManyChainMultiSig { ); } - // todo: make onlyOwner + // todo: test not onlyOwner fn set_config( ref self: ContractState, signer_addresses: Span, @@ -502,10 +495,9 @@ mod ManyChainMultiSig { let i = NUM_GROUPS - 1 - j; let group_parent = (*group_parents.at(i.into())); - assert( - (i == 0 || group_parent < i) && (i != 0 || group_parent == 0), - 'group tree malformed' - ); + let group_malformed = (i != 0 && group_parent >= i) + || (i == 0 && group_parent != 0); + assert(!group_malformed, 'group tree malformed'); let disabled = *group_quorums.at(i.into()) == 0; diff --git a/contracts/src/tests/test_mcms/test_set_config.cairo b/contracts/src/tests/test_mcms/test_set_config.cairo index 5ece62f25..00a4ddc02 100644 --- a/contracts/src/tests/test_mcms/test_set_config.cairo +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -676,7 +676,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 ) -> ( @@ -693,8 +692,6 @@ fn setup_2_of_2_mcms_no_root( ) { let (mcms_address, mcms, safe_mcms) = setup(); - // let signer_address_1: EthAddress = u256 { high: 0, low: 1 }.into(); - // let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); let signer_addresses: Array = array![signer_address_1, signer_address_2]; let signer_groups = array![0, 0]; let group_quorums = array![ @@ -848,10 +845,6 @@ fn test_set_config_success_dont_clear_root() { ] ); let config = mcms.get_config(); - // assert(config.signers == expected_config.signers, 'signers not equal'); - // assert(config.group_quorums == expected_config.group_quorums, 'group quorums not equal'); - // assert(config.group_parents == expected_config.group_parents, 'group parents not equal'); - // test the members assert(config == expected_config, 'config should be same'); // mock the contract state @@ -873,11 +866,11 @@ 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 1 {:?}", expected_signer_1); + // println!("signer 1 {:?}", signer_1); - println!("expected signer 2 {:?}", expected_signer_2); - println!("signer 2 {:?}", signer_2); + // 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'); @@ -921,11 +914,11 @@ 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 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); + // 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'); @@ -1073,4 +1066,3 @@ fn test_set_config_success_and_clear_root() { let s_root_metadata = state.s_root_metadata.read(); assert(expected_s_root_metadata == s_root_metadata, 's_root_metadata 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 b6281ffd7..149b96b30 100644 --- a/contracts/src/tests/test_mcms/test_set_root.cairo +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -24,7 +24,7 @@ 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; +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 snforge_std::{ @@ -33,7 +33,9 @@ use snforge_std::{ spy_events, EventSpyAssertionsTrait, // Add for assertions on the EventSpy test_address, // the contract being tested, start_cheat_chain_id, - cheatcodes::{events::{EventSpy}}, start_cheat_block_timestamp_global + cheatcodes::{events::{EventSpy}}, start_cheat_block_timestamp_global, + start_cheat_block_timestamp, start_cheat_account_contract_address_global, + start_cheat_account_contract_address }; // test to add root @@ -93,57 +95,92 @@ fn merkle_root(leafs: Array) -> (u256, Span) { (root, array![metadata_proof].span()) } -// sets up root -fn setup_2_of_2_mcms() -> ( - 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 signer_address_2: EthAddress = (0xDa09C953823E1F60916E85faD44bF99A7DACa267) - .try_into() - .unwrap(); - 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( - signer_address_1, signer_address_2 - ); +#[derive(Copy, Drop, Serde)] +struct SignerMetadata { + address: EthAddress, + private_key: u256 +} - let calldata = ArrayTrait::new(); - let mock_target_contract = declare("MockMultisigTarget").unwrap(); - let (target_address, _) = mock_target_contract.deploy(@calldata).unwrap(); - // mock chain id & timestamp +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) { let mock_chain_id = 732; - start_cheat_chain_id_global(mock_chain_id); - start_cheat_block_timestamp_global(3); + // 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: pre_op_count, + post_op_count: post_op_count, + 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) +} + + +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"); @@ -158,7 +195,6 @@ fn setup_2_of_2_mcms() -> ( }; // second operation - // todo update let selector2 = selector!("toggle"); let calldata2 = array![]; let op2 = Op { @@ -188,51 +224,90 @@ fn setup_2_of_2_mcms() -> ( 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(); - // abi.encode(root, valid_until) - // output: `sign this msg hash: 80900408832728789228986457074438699885219690940027717206734875544162448258660`` - println!("sign this msg hash: {:?}", encoded_root.keccak()); - - // run `yarn ts-node test/mcms-generate-signature.ts - - // signature 1: - // r: - // high: 0x547a06aecfe75c9bca6d20fb0571ca37, low: 0x1ba523139c3c00145f74f997b224c0ae - // s: - // high: 0x00696f026d9371e3009588d81391174f, low: 0xb85561559edf82cbebf145a8d77681a9 - // v: - // 27 - // signature 2: - // r: - // high: 0x73b0625d2623d18af2cbc023511386c7, low: 0x9d04510e392300e7a69dea2b89d83dd6 - // s: - // high: 0x55ce476d61b1cecdad20854760996cc7, low: 0x12754fc6e8ac845779e3d22d5a443957 - // v: - // 28 - - let signature1 = signature_from_vrs( - v: 27, - r: u256 { - high: 0x547a06aecfe75c9bca6d20fb0571ca37, low: 0x1ba523139c3c00145f74f997b224c0ae, - }, - s: u256 { - high: 0x00696f026d9371e3009588d81391174f, low: 0xb85561559edf82cbebf145a8d77681a9 - }, + 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() -> ( + 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 signer_metadata = array![ + SignerMetadata { address: signer_address_1, private_key: private_key_1 }, + SignerMetadata { address: signer_address_2, private_key: private_key_2 } + ]; + + 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( + signer_address_1, signer_address_2 ); - let signature2 = signature_from_vrs( - v: 28, - r: u256 { - high: 0x73b0625d2623d18af2cbc023511386c7, low: 0x9d04510e392300e7a69dea2b89d83dd6, - }, - s: u256 { - high: 0x55ce476d61b1cecdad20854760996cc7, low: 0x12754fc6e8ac845779e3d22d5a443957 - }, + 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 ); - let signatures = array![signature1, signature2]; + // mock chain id & timestamp + start_cheat_chain_id_global(metadata.chain_id.try_into().unwrap()); - let ops = array![op1.clone(), op2.clone()]; + let mock_timestamp = 3; + start_cheat_block_timestamp_global(mock_timestamp); ( spy, @@ -254,9 +329,8 @@ fn setup_2_of_2_mcms() -> ( ) } - // sets up root -fn new_setup_2_of_2_mcms() -> ( +fn new_setup_2_of_2_mcms_wrong_multisig() -> ( EventSpy, ContractAddress, IManyChainMultiSigDispatcher, @@ -337,7 +411,7 @@ fn new_setup_2_of_2_mcms() -> ( let metadata = RootMetadata { chain_id: mock_chain_id.into(), - multisig: mcms_address, + multisig: contract_address_const::<123123>(), // choose wrong multisig address pre_op_count: 0, post_op_count: 2, override_previous_root: false, @@ -420,6 +494,9 @@ fn test_set_root_success() { assert(actual_root == root, 'root returned not equal'); assert(actual_valid_until == valid_until, 'valid until not equal'); + let actual_root_metadata = mcms.get_root_metadata(); + assert(actual_root_metadata == metadata, 'root metadata not equal'); + spy .assert_emitted( @array![ @@ -560,4 +637,364 @@ fn test_set_root_signatures_invalid_signer() { } } +#[test] +#[feature("safe_dispatcher")] +fn test_insufficient_signers() { + 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(); + + let missing_1_signature = array![*signatures.at(0)]; + + let result = safe_mcms + .set_root(root, valid_until, metadata, metadata_proof, missing_1_signature); + + match result { + Result::Ok(_) => panic!("expect 'insufficient signers'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'insufficient signers', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_valid_until_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 + ) = + new_setup_2_of_2_mcms(); + + // cheat block timestamp + start_cheat_block_timestamp_global(valid_until.into() + 1); + + let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + match result { + Result::Ok(_) => panic!("expect 'valid until has passed'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'valid until has passed', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_invalid_metadata_proof() { + 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(); + + let invalid_metadata_proof = array![*metadata_proof.at(0), *metadata_proof.at(0)]; + + let result = safe_mcms + .set_root(root, valid_until, metadata, invalid_metadata_proof.span(), signatures); + + 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)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_invalid_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 + ) = + new_setup_2_of_2_mcms(); + + start_cheat_chain_id_global(123123); + + let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + 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_invalid_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 + ) = + new_setup_2_of_2_mcms_wrong_multisig(); + + let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + match result { + Result::Ok(_) => panic!("expect 'wrong multisig address'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'wrong multisig address', *panic_data.at(0)); + } + } +} + +#[test] +#[feature("safe_dispatcher")] +fn test_pending_ops_remain() { + 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(); + + // 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( + mcms_address, contract_address_const::<123123>(), signer_metadata + ); + + // second time fails + let result = safe_mcms.set_root(root, valid_until, metadata, metadata_proof, signatures); + + match result { + Result::Ok(_) => panic!("expect 'pending operations remain'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'pending operations remain', *panic_data.at(0)); + } + } +} + + +#[test] +#[feature("safe_dispatcher")] +fn test_wrong_pre_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)); + } + } +} +// todo: make signer metadata a constant so you don't need to repeat yourself + +// 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)); +// } +// } +// } From f4a164646ace6607dcc524d00cb8287b9736d449 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Mon, 16 Sep 2024 17:03:04 -0400 Subject: [PATCH 18/24] test set_config not owner --- contracts/src/mcms.cairo | 1 - .../src/tests/test_mcms/test_set_config.cairo | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index b3cc50238..50880e751 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -453,7 +453,6 @@ mod ManyChainMultiSig { ); } - // todo: test not onlyOwner fn set_config( ref self: ContractState, signer_addresses: Span, diff --git a/contracts/src/tests/test_mcms/test_set_config.cairo b/contracts/src/tests/test_mcms/test_set_config.cairo index 00a4ddc02..7a21eca57 100644 --- a/contracts/src/tests/test_mcms/test_set_config.cairo +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -56,6 +56,37 @@ fn setup() -> (ContractAddress, IManyChainMultiSigDispatcher, IManyChainMultiSig ) } +#[test] +#[feature("safe_dispatcher")] +fn test_not_owner() { + let (_, _, mcms_safe) = setup(); + + let signer_addresses = array![]; + let signer_groups = array![]; + let group_quorums = array![]; + let group_parents = array![]; + let clear_root = false; + + // so that caller is not owner + start_cheat_caller_address_global(contract_address_const::<123123>()); + + let result = mcms_safe + .set_config( + signer_addresses.span(), + signer_groups.span(), + group_quorums.span(), + group_parents.span(), + clear_root + ); + + match result { + Result::Ok(_) => panic!("expect 'Caller is not the owner'"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Caller is not the owner', *panic_data.at(0)); + } + } +} + #[test] #[feature("safe_dispatcher")] fn test_set_config_out_of_bound_signers() { From ee750326f30f3627b09a5bb0a622d75a626e38d6 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 17 Sep 2024 11:08:59 -0400 Subject: [PATCH 19/24] add proofs return value to helper func --- contracts/src/mcms.cairo | 2 - contracts/src/tests/test_mcms.cairo | 1 + .../src/tests/test_mcms/test_execute.cairo | 60 +++++++++++++++++++ .../src/tests/test_mcms/test_set_root.cairo | 43 +++++++------ 4 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 contracts/src/tests/test_mcms/test_execute.cairo diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 50880e751..3d27da42c 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -12,7 +12,6 @@ use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; #[starknet::interface] trait IManyChainMultiSig { - // todo: check length of byte array is 32 fn set_root( ref self: TContractState, root: u256, @@ -23,7 +22,6 @@ trait IManyChainMultiSig { signatures: Array ); fn execute(ref self: TContractState, op: Op, proof: Span); - // // todo: check length of group_quorums and group_parents fn set_config( ref self: TContractState, signer_addresses: Span, diff --git a/contracts/src/tests/test_mcms.cairo b/contracts/src/tests/test_mcms.cairo index 44e9ff10f..3c4ce69c6 100644 --- a/contracts/src/tests/test_mcms.cairo +++ b/contracts/src/tests/test_mcms.cairo @@ -1,4 +1,5 @@ mod test_set_config; mod test_set_root; +mod test_execute; mod utils; diff --git a/contracts/src/tests/test_mcms/test_execute.cairo b/contracts/src/tests/test_mcms/test_execute.cairo new file mode 100644 index 000000000..87fc1cc0c --- /dev/null +++ b/contracts/src/tests/test_mcms/test_execute.cairo @@ -0,0 +1,60 @@ +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 + }, + 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, + 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 +// } + diff --git a/contracts/src/tests/test_mcms/test_set_root.cairo b/contracts/src/tests/test_mcms/test_set_root.cairo index 149b96b30..84594c91e 100644 --- a/contracts/src/tests/test_mcms/test_set_root.cairo +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -51,25 +51,24 @@ use snforge_std::{ // https://www.npmjs.com/package/merkletreejs // the proof does not include the root or the leaf -// metadata should be the last leaf -// will only work with an even number of ops -fn merkle_root(leafs: Array) -> (u256, Span) { +// 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) { let mut level: Array = ArrayTrait::new(); - let odd = leafs.len() % 2 == 1; - let mut metadata: Option = Option::None; - if odd { - metadata = Option::Some(*leafs.at(leafs.len() - 1)); - let mut i = 0; + 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; - } - } + // 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(); + 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 @@ -87,12 +86,9 @@ fn merkle_root(leafs: Array) -> (u256, 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 = match metadata { - Option::Some(m) => hash_pair(*level.at(0), m), - Option::None => (*level.at(0)), - }; + let root = hash_pair(*level.at(0), metadata); - (root, array![metadata_proof].span()) + (root, array![metadata_proof].span(), proof1.span(), proof2.span()) } #[derive(Copy, Drop, Serde)] @@ -150,7 +146,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, _, _) = 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()); @@ -221,7 +217,7 @@ fn generate_set_root_params_1( 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, _, _) = 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()); @@ -424,7 +420,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, _, _) = 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()); @@ -998,3 +994,4 @@ fn test_wrong_pre_op_count() { // } // } + From c5df72f84123e7699465237aeeddd8524d6fb5cf Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 17 Sep 2024 15:25:16 -0400 Subject: [PATCH 20/24] 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 + ) +} From 002ba37fab8049b9e0f9839e1404ef7622eda127 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 17 Sep 2024 15:25:59 -0400 Subject: [PATCH 21/24] remove comments --- contracts/src/mcms.cairo | 3 --- contracts/src/tests/test_mcms/test_set_root.cairo | 6 ------ 2 files changed, 9 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index c5c63d2b4..3b0b489bc 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -52,8 +52,6 @@ struct RootMetadata { override_previous_root: bool } -// todo: maybe use copy -// todo: figure out how this works off-chain with MCMS since we have a new selector field here #[derive(Copy, Drop, Serde)] struct Op { chain_id: u256, @@ -144,7 +142,6 @@ const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP: u256 = const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: u256 = 0xe6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c; -// todo: make sure this is the right way to encode the struct fn hash_metadata(metadata: RootMetadata, valid_until: u32) -> u256 { let encoded_metadata: Bytes = BytesTrait::new_empty() .encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA) diff --git a/contracts/src/tests/test_mcms/test_set_root.cairo b/contracts/src/tests/test_mcms/test_set_root.cairo index e22058412..dc1b66a49 100644 --- a/contracts/src/tests/test_mcms/test_set_root.cairo +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -735,12 +735,6 @@ fn test_wrong_pre_op_count() { } } } -// todo: make signer metadata a constant so you don't need to repeat yourself - -// todo: do two executes in between and then set the wrong root - -// pre - 2 -// post - 1 #[test] #[feature("safe_dispatcher")] From 22d9aab4a3fd463ce206b5b77454235010ed8da4 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 17 Sep 2024 15:32:04 -0400 Subject: [PATCH 22/24] remove ts mods --- contracts/package.json | 3 +- contracts/test/mcms-generate-signature.ts | 59 ----------------------- tsconfig.json | 3 -- 3 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 contracts/test/mcms-generate-signature.ts diff --git a/contracts/package.json b/contracts/package.json index 03460f8f9..b8a109c9e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -5,8 +5,7 @@ "main": "index.js", "scripts": { "compile:solidity": "hardhat compile", - "test": "hardhat test", - "generate:signature": "ts-node ./mcms-generate-signature" + "test": "hardhat test" }, "author": "", "license": "MIT", diff --git a/contracts/test/mcms-generate-signature.ts b/contracts/test/mcms-generate-signature.ts deleted file mode 100644 index e55958afb..000000000 --- a/contracts/test/mcms-generate-signature.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Wallet, utils } from 'ethers' - -const SIGNER_ADDRESS_1 = '0x13Cf92228941e27eBce80634Eba36F992eCB148A' -const SIGNER_PRIVATE_KEY_1 = '0xf366414c9042ec470a8d92e43418cbf62caabc2bbc67e82bd530958e7fcaa688' -const SIGNER_ADDRESS_2 = '0xDa09C953823E1F60916E85faD44bF99A7DACa267' -const SIGNER_PRIVATE_KEY_2 = '0xed10b7a09dd0418ab35b752caffb70ee50bbe1fe25a2ebe8bba8363201d48527' - -const toBytes = (message: string): Uint8Array => { - const numBytes = message.length / 2 - const bytes = new Uint8Array(numBytes) - - for (let i = 0; i < numBytes; i++) { - const byteValue = parseInt(message.slice(2 * i, 2 * i + 2), 16) - bytes[i] = byteValue - } - - return bytes -} - -const generateSignature = async (merkleRoot: string) => { - const signer1 = new Wallet(new utils.SigningKey(SIGNER_PRIVATE_KEY_1)) - const signer2 = new Wallet(new utils.SigningKey(SIGNER_PRIVATE_KEY_2)) - - const signers = [signer1, signer2] - - const merkleRootHex = BigInt(merkleRoot).toString(16) - - const msgToSign = toBytes(merkleRootHex) - - for (let i = 0; i < signers.length; i++) { - const signature = utils.splitSignature(await signers[i].signMessage(msgToSign)) - - const highR = '0x' + signature.r.slice(2, 34); // First 32 characters (high u128) - const lowR = '0x' + signature.r.slice(34); // Last 32 characters (low u128) - - const highS = '0x' + signature.s.slice(2, 34); // First 32 characters (high u128) - const lowS = '0x' + signature.s.slice(34); // Last 32 characters (low u128) - - console.log(` - signature ${i + 1}: - r: - high: ${highR}, low: ${lowR} - s: - high: ${highS}, low: ${lowS} - v: - ${signature.v} - `) - } -} - - -// run scarb test -e chainlink::tests::test_mcms::test_set_root::test_set_root_success and observe standard out to get the merkle root -// then execute `yarn ts-node test/mcms-generate-signature.ts ` -const main = async () => { - - await generateSignature(process.argv[2]) -} - -main() diff --git a/tsconfig.json b/tsconfig.json index a92ff1c2f..0c5132c51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,9 +3,6 @@ "files": [], "include": [], "references": [ - { - "path": "./contracts" - }, { "path": "./packages-ts/starknet-gauntlet" }, From 35ab68d9f2087c4376291236b656635b613dfc11 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Tue, 17 Sep 2024 15:50:27 -0400 Subject: [PATCH 23/24] tidy up --- .../src/tests/test_mcms/test_set_root.cairo | 125 +----------------- contracts/src/tests/test_mcms/utils.cairo | 10 +- 2 files changed, 11 insertions(+), 124 deletions(-) diff --git a/contracts/src/tests/test_mcms/test_set_root.cairo b/contracts/src/tests/test_mcms/test_set_root.cairo index dc1b66a49..d8d893a9d 100644 --- a/contracts/src/tests/test_mcms/test_set_root.cairo +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -26,7 +26,7 @@ use chainlink::mcms::{ }; 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 + setup_mcms_deploy_set_config_and_set_root, set_root_args, merkle_root }; use snforge_std::{ @@ -40,118 +40,6 @@ use snforge_std::{ start_cheat_account_contract_address }; -// 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 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, 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: pre_op_count, - post_op_count: post_op_count, - 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) -} - // sets up root but with wrong multisig address in metadata fn setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG() -> ( EventSpy, @@ -670,9 +558,8 @@ fn test_pending_ops_remain() { // 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 (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = - set_root_args( - mcms_address, contract_address_const::<123123>(), signer_metadata + let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = set_root_args( + mcms_address, contract_address_const::<123123>(), signer_metadata, 0, 2 ); // second time fails @@ -715,8 +602,7 @@ fn test_wrong_pre_op_count() { 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, _, _) = - generate_set_root_params_custom_op_count( + let (root, valid_until, metadata, metadata_proof, signatures, _, _) = set_root_args( mcms_address, contract_address_const::<123123>(), signer_metadata, @@ -776,8 +662,7 @@ fn test_wrong_post_ops_count() { 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( + let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = set_root_args( mcms_address, contract_address_const::<123123>(), signer_metadata, diff --git a/contracts/src/tests/test_mcms/utils.cairo b/contracts/src/tests/test_mcms/utils.cairo index 014ab8309..cd34e64ef 100644 --- a/contracts/src/tests/test_mcms/utils.cairo +++ b/contracts/src/tests/test_mcms/utils.cairo @@ -184,7 +184,9 @@ fn merkle_root(leafs: Array) -> (u256, Span, Span>) { fn set_root_args( mcms_address: ContractAddress, target_address: ContractAddress, - mut signers_metadata: Array + mut signers_metadata: Array, + pre_op_count: u64, + post_op_count: u64 ) -> (u256, u32, RootMetadata, Span, Array, Array, Span>) { let mock_chain_id = 732; @@ -215,8 +217,8 @@ fn set_root_args( let metadata = RootMetadata { chain_id: mock_chain_id.into(), multisig: mcms_address, - pre_op_count: 0, - post_op_count: 2, + pre_op_count: pre_op_count, + post_op_count: post_op_count, override_previous_root: false, }; let valid_until = 9; @@ -428,7 +430,7 @@ fn setup_mcms_deploy_set_config_and_set_root() -> ( 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 + mcms_address, target_address, signer_metadata, 0, 2 ); // mock chain id & timestamp From f02d36d2624677f56008a7e7bd36470a22ac49a4 Mon Sep 17 00:00:00 2001 From: Augustus Chang Date: Wed, 18 Sep 2024 11:03:41 -0400 Subject: [PATCH 24/24] refactor tests --- contracts/src/mcms.cairo | 2 +- .../src/tests/test_mcms/test_execute.cairo | 5 +- .../src/tests/test_mcms/test_set_config.cairo | 369 +----------------- contracts/src/tests/test_mcms/utils.cairo | 109 ++---- 4 files changed, 60 insertions(+), 425 deletions(-) diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index 3b0b489bc..c7c5b4b87 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -466,7 +466,7 @@ mod ManyChainMultiSig { assert( group_quorums.len() == NUM_GROUPS.into() && group_quorums.len() == group_parents.len(), - 'group quorums/parents mismatch' + 'wrong group quorums/parents len' ); let mut group_children_counts: Felt252Dict = Default::default(); diff --git a/contracts/src/tests/test_mcms/test_execute.cairo b/contracts/src/tests/test_mcms/test_execute.cairo index 053f8c149..e4c588867 100644 --- a/contracts/src/tests/test_mcms/test_execute.cairo +++ b/contracts/src/tests/test_mcms/test_execute.cairo @@ -210,13 +210,12 @@ fn test_wrong_multisig_address() { 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::Ok(_) => panic!("expect 'wrong multisig address'"), Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'wrong chain id', *panic_data.at(0)); + assert(*panic_data.at(0) == 'wrong multisig address', *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 79db2d323..9d68415dd 100644 --- a/contracts/src/tests/test_mcms/test_set_config.cairo +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -23,7 +23,7 @@ use snforge_std::{ cheatcodes::{events::{EventSpy}} }; use chainlink::tests::test_mcms::utils::{ - setup_mcms_deploy, setup_mcms_deploy_and_set_config_2_of_2 + setup_mcms_deploy, setup_mcms_deploy_and_set_config_2_of_2, ZERO_ARRAY, fill_array }; #[test] @@ -165,21 +165,15 @@ fn test_set_config_group_quorums_parents_mismatch() { ); match result { - Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), + Result::Ok(_) => panic!("expect 'wrong group quorums/parents len'"), Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); + assert(*panic_data.at(0) == 'wrong group quorums/parents len', *panic_data.at(0)); } } // 5. test if group_quorum and group_parents not equal in length - // todo: replace with [0_u8; 32] in cairo 2.7.0 - let mut group_quorums = ArrayTrait::new(); - let mut i = 0; - while i < 32_usize { - group_quorums.append(0); - i += 1; - }; + let mut group_quorums = ZERO_ARRAY(); let result = mcms_safe .set_config( @@ -191,9 +185,9 @@ fn test_set_config_group_quorums_parents_mismatch() { ); match result { - Result::Ok(_) => panic!("expect 'group quorums/parents mismatch'"), + Result::Ok(_) => panic!("expect 'wrong group quorums/parents len'"), Result::Err(panic_data) => { - assert(*panic_data.at(0) == 'group quorums/parents mismatch', *panic_data.at(0)); + assert(*panic_data.at(0) == 'wrong group quorums/parents len', *panic_data.at(0)); } } } @@ -249,75 +243,8 @@ fn test_set_config_group_tree_malformed() { let signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; - let mut group_quorums = 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 mut 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, - 31 - ]; + let mut group_quorums = ZERO_ARRAY(); + let mut group_parents = fill_array(array![(31, 31)]); let clear_root = false; @@ -397,74 +324,8 @@ fn test_set_config_signer_in_disabled_group() { let mut signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; - let mut group_quorums = 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 mut 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 mut group_quorums = ZERO_ARRAY(); + let mut group_parents = ZERO_ARRAY(); let clear_root = false; let result = mcms_safe @@ -492,74 +353,8 @@ fn test_set_config_quorum_impossible() { let mut signer_addresses = array![EthAddressZeroable::zero()]; let signer_groups = array![0]; - let mut 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 mut 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 mut group_quorums = fill_array(array![(0, 2)]); + let mut group_parents = ZERO_ARRAY(); let clear_root = false; let result = mcms_safe @@ -590,74 +385,8 @@ fn test_set_config_signer_addresses_not_sorted() { u256 { high: 0, low: 1 }.into(), EthAddressZeroable::zero() ]; let signer_groups = array![0, 0]; - let mut 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 mut 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 mut group_quorums = fill_array(array![(0, 2)]); + let mut group_parents = ZERO_ARRAY(); let clear_root = false; let result = mcms_safe @@ -842,74 +571,8 @@ fn test_set_config_success_and_clear_root() { let signer_address_2: EthAddress = u256 { high: 0, low: 2 }.into(); 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 group_quorums = fill_array(array![(0, 2)]); + let group_parents = ZERO_ARRAY(); let clear_root = true; state diff --git a/contracts/src/tests/test_mcms/utils.cairo b/contracts/src/tests/test_mcms/utils.cairo index cd34e64ef..bbb1195a0 100644 --- a/contracts/src/tests/test_mcms/utils.cairo +++ b/contracts/src/tests/test_mcms/utils.cairo @@ -41,6 +41,44 @@ use snforge_std::{ // setup helpers // +// returns a length 32 array +// give (index, value) tuples to fill array with +// +// ex: fill_array(array!(0, 1)) will fill the 0th index with value 1 +// +// assumes that values array is sorted in ascending order of the index +fn fill_array(mut values: Array<(u32, u8)>) -> Array { + let mut result: Array = ArrayTrait::new(); + + let mut maybe_next = values.pop_front(); + + let mut i = 0; + while i < 32_u32 { + match maybe_next { + Option::Some(next) => { + let (next_index, next_value) = next; + + if i == next_index { + result.append(next_value); + maybe_next = values.pop_front(); + } else { + result.append(0); + } + }, + Option::None(_) => { result.append(0); }, + } + + i += 1; + }; + + result +} + +fn ZERO_ARRAY() -> Array { + // todo: replace with [0_u8; 32] in cairo 2.7.0+ + fill_array(array![]) +} + #[derive(Copy, Drop, Serde)] struct SignerMetadata { address: EthAddress, @@ -289,74 +327,9 @@ fn setup_mcms_deploy_and_set_config_2_of_2( 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 group_quorums = fill_array(array![(0, 2)]); + let group_parents = ZERO_ARRAY(); + let clear_root = false; let mut spy = spy_events();