From f0f42371e255cb7794fa2985062737a578c1f91a Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 16 Oct 2024 14:18:33 +0200 Subject: [PATCH 1/9] feat: ProtocolHandler --- .trunk/trunk.yaml | 1 + cairo/protocol_handler/.gitignore | 1 + cairo/protocol_handler/.tool-versions | 2 + cairo/protocol_handler/Scarb.lock | 125 ++++++ cairo/protocol_handler/Scarb.toml | 20 + .../src/kakarot_interface.cairo | 35 ++ cairo/protocol_handler/src/lib.cairo | 6 + .../src/protocol_handler.cairo | 292 +++++++++++++ cairo/protocol_handler/tests/lib.cairo | 1 + .../tests/test_protocol_handler.cairo | 405 ++++++++++++++++++ 10 files changed, 888 insertions(+) create mode 100644 cairo/protocol_handler/.gitignore create mode 100644 cairo/protocol_handler/.tool-versions create mode 100644 cairo/protocol_handler/Scarb.lock create mode 100644 cairo/protocol_handler/Scarb.toml create mode 100644 cairo/protocol_handler/src/kakarot_interface.cairo create mode 100644 cairo/protocol_handler/src/lib.cairo create mode 100644 cairo/protocol_handler/src/protocol_handler.cairo create mode 100644 cairo/protocol_handler/tests/lib.cairo create mode 100644 cairo/protocol_handler/tests/test_protocol_handler.cairo diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 134de54b1..af5622922 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -73,6 +73,7 @@ lint: - cairo/token - cairo/utils - cairo/kakarot-ssj/crates + - cairo/protocol_handler - linters: [ALL] paths: - logs* diff --git a/cairo/protocol_handler/.gitignore b/cairo/protocol_handler/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/cairo/protocol_handler/.gitignore @@ -0,0 +1 @@ +target diff --git a/cairo/protocol_handler/.tool-versions b/cairo/protocol_handler/.tool-versions new file mode 100644 index 000000000..5248f18a0 --- /dev/null +++ b/cairo/protocol_handler/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.8.3 +starknet-foundry 0.31.0 diff --git a/cairo/protocol_handler/Scarb.lock b/cairo/protocol_handler/Scarb.lock new file mode 100644 index 000000000..b9a2215c3 --- /dev/null +++ b/cairo/protocol_handler/Scarb.lock @@ -0,0 +1,125 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "openzeppelin" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_finance" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_token", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_presets" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_token" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_utils" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "protocol_handler" +version = "0.1.0" +dependencies = [ + "openzeppelin", + "snforge_std", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" + +[[package]] +name = "snforge_std" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/cairo/protocol_handler/Scarb.toml b/cairo/protocol_handler/Scarb.toml new file mode 100644 index 000000000..e3b961023 --- /dev/null +++ b/cairo/protocol_handler/Scarb.toml @@ -0,0 +1,20 @@ +[package] +name = "protocol_handler" +version = "0.1.0" +edition = "2023_10" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = "2.8.2" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.17.0" } + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } + +[[target.starknet-contract]] +casm = true +casm-add-pythonic-hints = true + +[scripts] +test = "snforge test --max-n-steps 4294967295" diff --git a/cairo/protocol_handler/src/kakarot_interface.cairo b/cairo/protocol_handler/src/kakarot_interface.cairo new file mode 100644 index 000000000..1ef16ca96 --- /dev/null +++ b/cairo/protocol_handler/src/kakarot_interface.cairo @@ -0,0 +1,35 @@ +use starknet::account::Call; +use starknet::{ContractAddress, ClassHash, EthAddress}; + +#[starknet::interface] +pub trait IKakarot { + //* ------------------------------------------------------------------------ *// + //* ADMIN FUNCTIONS *// + //* ------------------------------------------------------------------------ *// + fn upgrade(ref self: TContractState, new_class_hash: ClassHash); + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + fn pause(ref self: TContractState); + fn unpause(ref self: TContractState); + + //* ------------------------------------------------------------------------ *// + //* STORAGE SETTING FUNCTIONS *// + //* ------------------------------------------------------------------------ *// + fn set_native_token(ref self: TContractState, native_token: ContractAddress); + fn set_base_fee(ref self: TContractState, base_fee: u64); + fn set_coinbase(ref self: TContractState, new_coinbase: ContractAddress); + fn set_prev_randao(ref self: TContractState, pre_randao: felt252); + fn set_block_gas_limit(ref self: TContractState, new_block_gas_limit: felt252); + fn set_account_contract_class_hash(ref self: TContractState, new_class_hash: felt252); + fn set_uninitialized_account_class_hash(ref self: TContractState, new_class_hash: felt252); + fn set_authorized_cairo_precompile_caller( + ref self: TContractState, evm_address: ContractAddress, authorized: bool + ); + fn set_cairo1_helpers_class_hash(ref self: TContractState, new_class_hash: felt252); + fn upgrade_account(ref self: TContractState, evm_address: ContractAddress, new_class: felt252); + fn set_authorized_pre_eip155_tx( + ref self: TContractState, sender_address: ContractAddress, msg_hash: felt252 + ); + fn set_l1_messaging_contract_address( + ref self: TContractState, l1_messaging_contract_address: ContractAddress + ); +} diff --git a/cairo/protocol_handler/src/lib.cairo b/cairo/protocol_handler/src/lib.cairo new file mode 100644 index 000000000..3cce19e96 --- /dev/null +++ b/cairo/protocol_handler/src/lib.cairo @@ -0,0 +1,6 @@ +mod protocol_handler; +pub use protocol_handler::{ + ProtocolHandler, IProtocolHandler, IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait +}; + +mod kakarot_interface; diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo new file mode 100644 index 000000000..8386699ee --- /dev/null +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -0,0 +1,292 @@ +use starknet::account::Call; +use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +trait IProtocolHandler { + /// Execute a call to the Kakarot contract. + /// Only the security council can call this function. + /// # Arguments + /// * `call` - The call to be executed + /// + /// # Panics + /// * `Caller is missing role` in case the caller is not the security council + /// * `ONLY_KAKAROT_CAN_BE_CALLED` in case the call is not to the Kakarot contract + /// + fn emergency_execution(ref self: TContractState, call: Call); + + /// Upgrade the Kakarot contract to a new version. + /// Only the operator can call this function. + /// # Arguments + /// * `new_class_hash` - The new class hash of the Kakarot contract + /// + /// # Panics + /// * `Caller is missing role` in case the caller is not the operator + fn upgrade(ref self: TContractState, new_class_hash: ClassHash); + + /// Transfer the ownership of the Kakarot contract. + /// Only the security council can call this function. + /// # Arguments + /// * `new_owner` - The new owner of the Kakarot contract + /// + /// # Panics + /// * `Caller is missing role` in case the caller is not the security council + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + + /// Pause the protocol for SOFT_PAUSE_DELAY. + /// Only the guardians can call this function. + /// # Panics + /// * `Caller is missing role` in case the caller is not a guardian + fn soft_pause(ref self: TContractState); + + /// Pause the protocol for HARD_PAUSE_DELAY. + /// Only the security council can call this function. + /// # Panics + /// * `Caller is missing role` in case the caller is not the security council + fn hard_pause(ref self: TContractState); + + /// Unpause the protocol. + /// Only the security council can call this function if the delay is not passed. + /// Else anyone can call this function. + /// # Panics + /// * `Caller is missing role` in case the caller is not the security council + fn unpause(ref self: TContractState); +} + +#[starknet::contract] +mod ProtocolHandler { + use starknet::event::EventEmitter; + use starknet::account::Call; + use starknet::{ContractAddress, ClassHash, get_block_timestamp, SyscallResultTrait}; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + use crate::kakarot_interface::{IKakarotDispatcher, IKakarotDispatcherTrait}; + + + //* ------------------------------------------------------------------------ *// + //* COMPONENTS *// + //* ------------------------------------------------------------------------ *// + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + + //* ------------------------------------------------------------------------ *// + //* CONSTANTS *// + //* ------------------------------------------------------------------------ *// + + // Access controls roles + const SECURITY_COUNCIL_ROLE: felt252 = selector!("SECURITY_COUNCIL_ROLE"); + const GUARDIAN_ROLE: felt252 = selector!("GUARDIAN_ROLE"); + const OPERATOR_ROLE: felt252 = selector!("OPERATOR_ROLE"); + // Pause delay + const SOFT_PAUSE_DELAY: u64 = 12 * 60 * 60; // 12 hours + const HARD_PAUSE_DELAY: u64 = 7 * 24 * 60 * 60; // 7 days + + //* ------------------------------------------------------------------------ *// + //* STORAGE *// + //* ------------------------------------------------------------------------ *// + + #[storage] + pub struct Storage { + Kakarot: ContractAddress, + Operator: ContractAddress, + Guardians: Map, + ProtocolFrozenUntil: u64, + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + } + + //* ------------------------------------------------------------------------ *// + //* EVENTS *// + //* ------------------------------------------------------------------------ *// + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + EmergencyExecution: EmergencyExecution, + Upgrade: Upgrade, + TransferOwnership: TransferOwnership, + SoftPause: SoftPause, + HardPause: HardPause, + Unpause: Unpause, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[derive(Drop, starknet::Event)] + struct EmergencyExecution { + call: Call + } + + #[derive(Drop, starknet::Event)] + struct Upgrade { + new_class_hash: ClassHash + } + + #[derive(Drop, starknet::Event)] + struct TransferOwnership { + new_owner: ContractAddress + } + + #[derive(Drop, starknet::Event)] + struct SoftPause { + protocol_frozen_until: u64, + } + + #[derive(Drop, starknet::Event)] + struct HardPause { + protocol_frozen_until: u64, + } + + #[derive(Drop, starknet::Event)] + struct Unpause {} + + //* ------------------------------------------------------------------------ *// + //* CONSTRUCTOR *// + //* ------------------------------------------------------------------------ *// + + #[constructor] + fn constructor( + ref self: ContractState, + kakarot_: ContractAddress, + security_council_: ContractAddress, + operator_: ContractAddress, + mut guardians_: Span, + ) { + // Store the Kakarot address + self.Kakarot.write(kakarot_); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + + // Grant roles + self.accesscontrol._grant_role(SECURITY_COUNCIL_ROLE, security_council_); + self.accesscontrol._grant_role(OPERATOR_ROLE, operator_); + for guardian in guardians_ { + self.accesscontrol._grant_role(GUARDIAN_ROLE, *guardian); + }; + } + + #[abi(embed_v0)] + impl ProtocolHandler of super::IProtocolHandler { + //* ------------------------------------------------------------------------ *// + //* ADMIN FUNCTIONS *// + //* ------------------------------------------------------------------------ *// + + fn emergency_execution(ref self: ContractState, call: Call) { + // Check only security council can call + self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); + + // Check if the call is to the Kakarot + let kakarot = self.Kakarot.read(); + let Call { to, selector, calldata } = call; + assert(to == kakarot, 'ONLY_KAKAROT_CAN_BE_CALLED'); + + // Call Kakarot with syscall + starknet::syscalls::call_contract_syscall(to, selector, calldata).unwrap_syscall(); + + // Emit EmergencyExecution event + self.emit(EmergencyExecution { call }); + } + + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + // Check only operator can call + self.accesscontrol.assert_only_role(OPERATOR_ROLE); + + // Call the Kakarot upgrade function + let kakarot = self.get_kakarot_dispatcher(); + kakarot.upgrade(new_class_hash); + + // Emit Upgrade event + self.emit(Upgrade { new_class_hash }); + } + + fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { + // Check only security council can call + self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); + + // Call the Kakarot transfer_ownership function + let kakarot = self.get_kakarot_dispatcher(); + kakarot.transfer_ownership(new_owner); + + // Emit TransferOwnership event + self.emit(TransferOwnership { new_owner }); + } + + fn soft_pause(ref self: ContractState) { + // Check only guardians can call + self.accesscontrol.assert_only_role(GUARDIAN_ROLE); + + // Cache the protocol frozen until timestamp + let protocolFrozenUntil = get_block_timestamp().into() + SOFT_PAUSE_DELAY; + + // Update storage + self.ProtocolFrozenUntil.write(protocolFrozenUntil); + + // Call the Kakarot pause function + let kakarot = self.get_kakarot_dispatcher(); + kakarot.pause(); + + // Emit SoftPause event + self.emit(SoftPause { protocol_frozen_until: protocolFrozenUntil }); + } + + fn hard_pause(ref self: ContractState) { + // Check only security council can call + self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); + + // Cache the protocol frozen until timestamp + let protocolFrozenUntil = get_block_timestamp().into() + HARD_PAUSE_DELAY; + + // Update storage + self.ProtocolFrozenUntil.write(protocolFrozenUntil); + + // Call the Kakarot pause function + let kakarot = self.get_kakarot_dispatcher(); + kakarot.pause(); + + // Emit HardPause event + self.emit(HardPause { protocol_frozen_until: protocolFrozenUntil }); + } + + fn unpause(ref self: ContractState) { + // Check only security council can call unpause if delay is not passed + let too_soon = get_block_timestamp().into() < self.ProtocolFrozenUntil.read(); + if too_soon { + self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); + } + + // Call the Kakarot unpause function + let kakarot = self.get_kakarot_dispatcher(); + kakarot.unpause(); + + // Update storage + self.ProtocolFrozenUntil.write(0); + + // Emit Unpause event + self.emit(Unpause {}); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn get_kakarot_dispatcher(ref self: ContractState) -> IKakarotDispatcher { + let kakarot_address = self.Kakarot.read(); + IKakarotDispatcher { contract_address: kakarot_address } + } + } +} diff --git a/cairo/protocol_handler/tests/lib.cairo b/cairo/protocol_handler/tests/lib.cairo new file mode 100644 index 000000000..464ffc0c2 --- /dev/null +++ b/cairo/protocol_handler/tests/lib.cairo @@ -0,0 +1 @@ +mod test_protocol_handler; diff --git a/cairo/protocol_handler/tests/test_protocol_handler.cairo b/cairo/protocol_handler/tests/test_protocol_handler.cairo new file mode 100644 index 000000000..8f982ebfa --- /dev/null +++ b/cairo/protocol_handler/tests/test_protocol_handler.cairo @@ -0,0 +1,405 @@ +use snforge_std::{ + ContractClassTrait, ContractClass, declare, DeclareResultTrait, EventSpyAssertionsTrait, + start_cheat_block_timestamp_global, start_cheat_caller_address, mock_call, spy_events, store, + load +}; +use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; +use starknet::account::Call; +use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; +use starknet::Felt252TryIntoContractAddress; +use protocol_handler::{ + IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, IProtocolHandler, ProtocolHandler +}; + +fn setup_contracts_for_testing() -> ( + IProtocolHandlerDispatcher, + ContractClass, + ContractAddress, + ContractAddress, + ContractAddress, + Span +) { + // Mock Kakarot, security council, operator and guardians + let kakarot_mock: ContractAddress = contract_address_const::<'kakarot_mock'>(); + let security_council_mock: ContractAddress = contract_address_const::< + 'security_council_mock' + >(); + let operator_mock: ContractAddress = contract_address_const::<'operator_mock'>(); + let guardians: Span = array![ + contract_address_const::<'guardian_mock_1'>(), contract_address_const::<'guardian_mock_2'>() + ] + .span(); + + // Construct the calldata for the ProtocolHandler contrustor + let mut constructor_calldata: Array:: = array![ + kakarot_mock.into(), security_council_mock.into(), operator_mock.into() + ]; + Serde::serialize(@guardians, ref constructor_calldata); + let contract = declare("ProtocolHandler").unwrap().contract_class(); + let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); + + // Get The dispatcher for the ProtocolHandler + let protocol_handler = IProtocolHandlerDispatcher { contract_address }; + + return ( + protocol_handler, *contract, kakarot_mock, security_council_mock, operator_mock, guardians + ); +} + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_emergency_execution_fail_wrong_caller() { + let (protocol_handler, _, kakarot_mock, _, _, _) = setup_contracts_for_testing(); + + // Change caller to random caller address + let random_caller = contract_address_const::<'random_caller'>(); + start_cheat_caller_address(protocol_handler.contract_address, random_caller); + + // Call emergency_execution, should fail as caller is not security council + let call = Call { to: kakarot_mock, selector: 0, calldata: [].span() }; + protocol_handler.emergency_execution(call); +} + +#[test] +#[should_panic(expected: 'ONLY_KAKAROT_CAN_BE_CALLED')] +fn test_protocol_emergency_execution_fail_wrong_destination() { + let (protocol_handler, _, _, security_council_mock, _, _) = setup_contracts_for_testing(); + + // Change caller to security council + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + + // Construct the Call to a random address + let random_called_address = contract_address_const::<'random_called_address'>(); + let call = Call { to: random_called_address, selector: 0, calldata: [].span() }; + + // Call emergency_execution, should fail as the call is not to Kakarot + protocol_handler.emergency_execution(call); +} + +#[test] +fn test_protocol_emergency_execution_should_pass() { + let (protocol_handler, contract, kakarot_mock, security_council_mock, _, _) = + setup_contracts_for_testing(); + + // Change caller to security council + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + + // Mock the call to Kakarot upgrade function + mock_call::<()>(kakarot_mock, selector!("upgrade"), (), 1); + + // Construct the Call to protocol handler and call emergency_execution + // Should pass as caller is security council and call is to Kakarot + let calldata = contract.class_hash; + let mut serialized_calldata: Array:: = array![]; + Serde::serialize(@calldata, ref serialized_calldata); + let call = Call { + to: kakarot_mock, selector: selector!("upgrade"), calldata: serialized_calldata.span() + }; + + // Spy on the events + let mut spy = spy_events(); + protocol_handler.emergency_execution(call); + + // Check the EmergencyExecution event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::EmergencyExecution( + ProtocolHandler::EmergencyExecution { call: call } + ) + ) + ] + ); +} + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_upgrade_fail_wrong_caller() { + let (protocol_handler, contract, _, _, _, _) = setup_contracts_for_testing(); + + // Change caller to random caller address + let random_caller = contract_address_const::<'random_caller'>(); + start_cheat_caller_address(protocol_handler.contract_address, random_caller); + + // Call the protocol handler upgrade, should fail as caller is not operator + protocol_handler.upgrade(contract.class_hash); +} + +fn test_protocol_upgrade_should_pass() { + let (protocol_handler, contract, kakarot_mock, _, operator_mock, _) = + setup_contracts_for_testing(); + + // Mock the call to Kakarot upgrade function + mock_call::<()>(kakarot_mock, selector!("upgrade"), (), 1); + + // Change caller to security council + start_cheat_caller_address(protocol_handler.contract_address, operator_mock); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler upgrade, should pass as caller is operator + protocol_handler.upgrade(contract.class_hash); + + // Check the TransferOwnership event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::Upgrade( + ProtocolHandler::Upgrade { new_class_hash: contract.class_hash } + ) + ) + ] + ); +} + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_handler_transfer_ownership_should_fail_wrong_caller() { + let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + + // Change caller to not security council + let not_security_council = contract_address_const::<'not_security_council'>(); + start_cheat_caller_address(protocol_handler.contract_address, not_security_council); + + // Call the protocol handler transfer_ownership, should fail as caller is not security council + let new_owner = contract_address_const::<'new_owner'>(); + protocol_handler.transfer_ownership(new_owner); +} + +#[test] +fn test_protocol_handler_transfer_ownership_should_pass() { + let (protocol_handler, _, kakarot_mock, security_council_mock, _, _) = + setup_contracts_for_testing(); + + // Change caller to security council + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + + // Mock the call to Kakarot transfer_ownership + mock_call::<()>(kakarot_mock, selector!("transfer_ownership"), (), 1); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler transfer_ownership + let new_owner = contract_address_const::<'new_owner'>(); + protocol_handler.transfer_ownership(new_owner); + + // Check the TransferOwnership event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::TransferOwnership( + ProtocolHandler::TransferOwnership { new_owner: new_owner } + ) + ) + ] + ); +} + + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_handler_soft_pause_should_fail_wrong_caller() { + let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + + // Change caller to random caller address + let random_caller = contract_address_const::<'random_caller'>(); + start_cheat_caller_address(protocol_handler.contract_address, random_caller); + + // Call the protocol handler soft_pause, should fail as caller is not guardian + protocol_handler.soft_pause(); +} + +#[test] +fn test_protocol_handler_soft_pause_should_pass() { + let (protocol_handler, _, kakarot_mock, _, _, guardians) = setup_contracts_for_testing(); + + // Mock the call to kakarot pause function + mock_call::<()>(kakarot_mock, selector!("pause"), (), 1); + + // Change caller to guardian + start_cheat_caller_address(protocol_handler.contract_address, *guardians[0]); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler soft_pause, should pass as caller is guardian + protocol_handler.soft_pause(); + + // Check the SoftPause event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::SoftPause( + ProtocolHandler::SoftPause { + protocol_frozen_until: ProtocolHandler::SOFT_PAUSE_DELAY // Blocktimestamp is 0 in tests + } + ) + ) + ] + ); +} + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_handler_hard_pause_should_fail_wrong_caller() { + let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + + // Change caller to not security council + let not_security_council = contract_address_const::<'not_security_council'>(); + start_cheat_caller_address(protocol_handler.contract_address, not_security_council); + + // Call the protocol handler hard_pause, should fail as caller is not security council + protocol_handler.hard_pause(); +} + +#[test] +fn test_protocol_handler_hard_pause_should_pass() { + let (protocol_handler, _, kakarot_mock, security_council_mock, _, _) = + setup_contracts_for_testing(); + + // Change caller to security council + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + + // Mock the call to kakarot pause function + mock_call::<()>(kakarot_mock, selector!("pause"), (), 1); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler hard_pause, should pass as caller is security council + protocol_handler.hard_pause(); + + // Check the HardPause event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::HardPause( + ProtocolHandler::HardPause { + protocol_frozen_until: ProtocolHandler::HARD_PAUSE_DELAY // Blocktimestamp is 0 in tests + } + ) + ) + ] + ); +} + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_handler_unpause_should_fail_wrong_caller_when_too_soon() { + // Block timestamp is 0 in tests, changing it to 10 + start_cheat_block_timestamp_global(10); + let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + + // Simulate pausing by writing in the storage + // Find the storage address for the ProtocolFrozenUntil + let mut state = ProtocolHandler::contract_state_for_testing(); + let storage_address = state.ProtocolFrozenUntil; + let value = (get_block_timestamp() * 10); + let mut serialized_value: Array:: = array![]; + Serde::serialize(@value, ref serialized_value); + // Store the value in the storage of the protocol handler + store( + protocol_handler.contract_address, storage_address.__base_address__, serialized_value.span() + ); + + // Change caller to random caller address + let random_caller = contract_address_const::<'random_caller'>(); + start_cheat_caller_address(protocol_handler.contract_address, random_caller); + + // Call the protocol handler unpause, should fail as caller is not security council + protocol_handler.unpause(); +} + +#[test] +fn test_protocol_handler_unpause_should_pass_security_council() { + // Block timestamp is 0 in tests changing it to 10 + start_cheat_block_timestamp_global(10); + let (protocol_handler, _, kakarot_mock, security_council_mock, _, _) = + setup_contracts_for_testing(); + + // Simulate pausing by writing in the storage + // Find the storage address for the ProtocolFrozenUntil + let mut state = ProtocolHandler::contract_state_for_testing(); + let storage_address = state.ProtocolFrozenUntil; + let value = (get_block_timestamp() * 10); + let mut serialized_value: Array:: = array![]; + Serde::serialize(@value, ref serialized_value); + // Store the value in the storage of the protocol handler + store( + protocol_handler.contract_address, storage_address.__base_address__, serialized_value.span() + ); + + // Change the caller to security council + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + + // Mock call to kakarot unpause function + mock_call::<()>(kakarot_mock, selector!("unpause"), (), 1); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler unpause, should pass as caller is security council + protocol_handler.unpause(); + + // Check the Unpause event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::Unpause(ProtocolHandler::Unpause {}) + ) + ] + ); +} + +#[test] +fn test_protocol_handler_unpause_should_pass_after_delay() { + // Block timestamp is 0 in tests + start_cheat_block_timestamp_global(10); + let (protocol_handler, _, kakarot_mock, _, _, _) = setup_contracts_for_testing(); + + // Simulate pausing by writing in the storage + // Find the storage address for the ProtocolFrozenUntil + let mut state = ProtocolHandler::contract_state_for_testing(); + let storage_address = state.ProtocolFrozenUntil; + let value = get_block_timestamp() - 1; + let mut serialized_value: Array:: = array![]; + Serde::serialize(@value, ref serialized_value); + // Store the value in the storage of the protocol handler + store( + protocol_handler.contract_address, storage_address.__base_address__, serialized_value.span() + ); + + // Mock call to kakarot unpause function + mock_call::<()>(kakarot_mock, selector!("unpause"), (), 1); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler unpause, should pass as caller is security council + protocol_handler.unpause(); + + // Check the Unpause event is emitted + spy + .assert_emitted( + @array![ + ( + protocol_handler.contract_address, + ProtocolHandler::Event::Unpause(ProtocolHandler::Unpause {}) + ) + ] + ); +} From da196ac728b887611e4b8e4cb9835fc83602eaac Mon Sep 17 00:00:00 2001 From: Oba Date: Tue, 22 Oct 2024 14:32:00 +0200 Subject: [PATCH 2/9] ci: protocolHandler --- .github/workflows/cairo-ci.yml | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/cairo-ci.yml diff --git a/.github/workflows/cairo-ci.yml b/.github/workflows/cairo-ci.yml new file mode 100644 index 000000000..029e40c93 --- /dev/null +++ b/.github/workflows/cairo-ci.yml @@ -0,0 +1,45 @@ +name: cairo-CI + +on: + push: + branches: + - main + pull_request: + branches: + - "*" +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +concurrency: + group: cairo-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: read-all + +jobs: + paths-filter: + runs-on: ubuntu-latest + outputs: + protocol_handler: ${{ steps.filter.outputs.protocol_handler }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + protocol_handler: + - 'cairo/protocol_handler/**' + + tests-unit: + runs-on: ubuntu-latest + needs: paths-filter + if: needs.paths-filter.outputs.protocol_handler == 'true' + steps: + - uses: actions/checkout@v4 + - uses: foundry-rs/setup-snfoundry@v3 + - uses: software-mansion/setup-scarb@v1 + with: + tool-versions: ./cairo/protocol_handler/.tool-versions + + - name: Run tests + run: cd cairo/protocol_handler && scarb test From 76f4ddfefaa5b4f226b1d6cc1de56db256defa54 Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 23 Oct 2024 15:19:54 +0200 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Mathieu <60658558+enitrat@users.noreply.github.com> --- cairo/protocol_handler/Scarb.toml | 4 +- .../src/protocol_handler.cairo | 56 ++++++++----------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/cairo/protocol_handler/Scarb.toml b/cairo/protocol_handler/Scarb.toml index e3b961023..f9132441e 100644 --- a/cairo/protocol_handler/Scarb.toml +++ b/cairo/protocol_handler/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "protocol_handler" version = "0.1.0" -edition = "2023_10" +edition = "2024_07" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html @@ -17,4 +17,4 @@ casm = true casm-add-pythonic-hints = true [scripts] -test = "snforge test --max-n-steps 4294967295" +test = "snforge test" diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo index 8386699ee..90c9305c5 100644 --- a/cairo/protocol_handler/src/protocol_handler.cairo +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -98,10 +98,10 @@ mod ProtocolHandler { #[storage] pub struct Storage { - Kakarot: ContractAddress, - Operator: ContractAddress, - Guardians: Map, - ProtocolFrozenUntil: u64, + kakarot: IKakarotDispatcher, + operator: ContractAddress, + guardians: Map, + protocol_frozen_until: u64, #[substorage(v0)] accesscontrol: AccessControlComponent::Storage, #[substorage(v0)] @@ -162,21 +162,21 @@ mod ProtocolHandler { #[constructor] fn constructor( ref self: ContractState, - kakarot_: ContractAddress, - security_council_: ContractAddress, - operator_: ContractAddress, - mut guardians_: Span, + kakarot: ContractAddress, + security_council: ContractAddress, + operator: ContractAddress, + mut guardians: Span, ) { // Store the Kakarot address - self.Kakarot.write(kakarot_); + self.kakarot.write(IKakarotDispatcher { contract_address: kakarot }); // AccessControl-related initialization self.accesscontrol.initializer(); // Grant roles - self.accesscontrol._grant_role(SECURITY_COUNCIL_ROLE, security_council_); - self.accesscontrol._grant_role(OPERATOR_ROLE, operator_); - for guardian in guardians_ { + self.accesscontrol._grant_role(SECURITY_COUNCIL_ROLE, security_council); + self.accesscontrol._grant_role(OPERATOR_ROLE, operator); + for guardian in guardians { self.accesscontrol._grant_role(GUARDIAN_ROLE, *guardian); }; } @@ -191,8 +191,8 @@ mod ProtocolHandler { // Check only security council can call self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); - // Check if the call is to the Kakarot - let kakarot = self.Kakarot.read(); + // Check if the call is to the Kakarot contract + let kakarot = self.kakarot.read().contract_address; let Call { to, selector, calldata } = call; assert(to == kakarot, 'ONLY_KAKAROT_CAN_BE_CALLED'); @@ -208,7 +208,7 @@ mod ProtocolHandler { self.accesscontrol.assert_only_role(OPERATOR_ROLE); // Call the Kakarot upgrade function - let kakarot = self.get_kakarot_dispatcher(); + let kakarot = self.kakarot.read(); kakarot.upgrade(new_class_hash); // Emit Upgrade event @@ -220,7 +220,7 @@ mod ProtocolHandler { self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); // Call the Kakarot transfer_ownership function - let kakarot = self.get_kakarot_dispatcher(); + let kakarot = self.kakarot.read(); kakarot.transfer_ownership(new_owner); // Emit TransferOwnership event @@ -232,10 +232,10 @@ mod ProtocolHandler { self.accesscontrol.assert_only_role(GUARDIAN_ROLE); // Cache the protocol frozen until timestamp - let protocolFrozenUntil = get_block_timestamp().into() + SOFT_PAUSE_DELAY; + let protocol_frozen_until = get_block_timestamp().into() + SOFT_PAUSE_DELAY; // Update storage - self.ProtocolFrozenUntil.write(protocolFrozenUntil); + self.protocol_frozen_until.write(protocol_frozen_until); // Call the Kakarot pause function let kakarot = self.get_kakarot_dispatcher(); @@ -250,13 +250,13 @@ mod ProtocolHandler { self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); // Cache the protocol frozen until timestamp - let protocolFrozenUntil = get_block_timestamp().into() + HARD_PAUSE_DELAY; + let protocol_frozen_until = get_block_timestamp().into() + HARD_PAUSE_DELAY; // Update storage - self.ProtocolFrozenUntil.write(protocolFrozenUntil); + self.protocol_frozen_until.write(protocol_frozen_until); // Call the Kakarot pause function - let kakarot = self.get_kakarot_dispatcher(); + let kakarot = self.kakarot.read(); kakarot.pause(); // Emit HardPause event @@ -265,28 +265,20 @@ mod ProtocolHandler { fn unpause(ref self: ContractState) { // Check only security council can call unpause if delay is not passed - let too_soon = get_block_timestamp().into() < self.ProtocolFrozenUntil.read(); + let too_soon = get_block_timestamp().into() < self.protocol_frozen_until.read(); if too_soon { self.accesscontrol.assert_only_role(SECURITY_COUNCIL_ROLE); } // Call the Kakarot unpause function - let kakarot = self.get_kakarot_dispatcher(); + let kakarot = self.kakarot.read(); kakarot.unpause(); // Update storage - self.ProtocolFrozenUntil.write(0); + self.protocol_frozen_until.write(0); // Emit Unpause event self.emit(Unpause {}); } } - #[generate_trait] - impl InternalImpl of InternalTrait { - fn get_kakarot_dispatcher(ref self: ContractState) -> IKakarotDispatcher { - let kakarot_address = self.Kakarot.read(); - IKakarotDispatcher { contract_address: kakarot_address } - } - } -} From 865e70970bb12628f06b27e17638fd18bf323616 Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 23 Oct 2024 15:16:40 +0200 Subject: [PATCH 4/9] apply suggestion fix --- .../src/kakarot_interface.cairo | 3 +- cairo/protocol_handler/src/lib.cairo | 1 - .../src/protocol_handler.cairo | 50 +++++++++---------- .../tests/test_protocol_handler.cairo | 13 ++--- 4 files changed, 31 insertions(+), 36 deletions(-) diff --git a/cairo/protocol_handler/src/kakarot_interface.cairo b/cairo/protocol_handler/src/kakarot_interface.cairo index 1ef16ca96..c4ab74dfa 100644 --- a/cairo/protocol_handler/src/kakarot_interface.cairo +++ b/cairo/protocol_handler/src/kakarot_interface.cairo @@ -1,5 +1,4 @@ -use starknet::account::Call; -use starknet::{ContractAddress, ClassHash, EthAddress}; +use starknet::{ContractAddress, ClassHash}; #[starknet::interface] pub trait IKakarot { diff --git a/cairo/protocol_handler/src/lib.cairo b/cairo/protocol_handler/src/lib.cairo index 3cce19e96..f721ac330 100644 --- a/cairo/protocol_handler/src/lib.cairo +++ b/cairo/protocol_handler/src/lib.cairo @@ -2,5 +2,4 @@ mod protocol_handler; pub use protocol_handler::{ ProtocolHandler, IProtocolHandler, IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait }; - mod kakarot_interface; diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo index 90c9305c5..c8a0f0346 100644 --- a/cairo/protocol_handler/src/protocol_handler.cairo +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -2,7 +2,7 @@ use starknet::account::Call; use starknet::{ContractAddress, ClassHash}; #[starknet::interface] -trait IProtocolHandler { +pub trait IProtocolHandler { /// Execute a call to the Kakarot contract. /// Only the security council can call this function. /// # Arguments @@ -53,12 +53,12 @@ trait IProtocolHandler { } #[starknet::contract] -mod ProtocolHandler { +pub mod ProtocolHandler { use starknet::event::EventEmitter; use starknet::account::Call; use starknet::{ContractAddress, ClassHash, get_block_timestamp, SyscallResultTrait}; use starknet::storage::{ - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + Map, StoragePointerReadAccess, StoragePointerWriteAccess }; use openzeppelin_access::accesscontrol::AccessControlComponent; @@ -89,8 +89,8 @@ mod ProtocolHandler { const GUARDIAN_ROLE: felt252 = selector!("GUARDIAN_ROLE"); const OPERATOR_ROLE: felt252 = selector!("OPERATOR_ROLE"); // Pause delay - const SOFT_PAUSE_DELAY: u64 = 12 * 60 * 60; // 12 hours - const HARD_PAUSE_DELAY: u64 = 7 * 24 * 60 * 60; // 7 days + pub const SOFT_PAUSE_DELAY: u64 = 12 * 60 * 60; // 12 hours + pub const HARD_PAUSE_DELAY: u64 = 7 * 24 * 60 * 60; // 7 days //* ------------------------------------------------------------------------ *// //* STORAGE *// @@ -98,10 +98,10 @@ mod ProtocolHandler { #[storage] pub struct Storage { - kakarot: IKakarotDispatcher, - operator: ContractAddress, - guardians: Map, - protocol_frozen_until: u64, + pub kakarot: IKakarotDispatcher, + pub operator: ContractAddress, + pub guardians: Map, + pub protocol_frozen_until: u64, #[substorage(v0)] accesscontrol: AccessControlComponent::Storage, #[substorage(v0)] @@ -114,7 +114,7 @@ mod ProtocolHandler { #[event] #[derive(Drop, starknet::Event)] - enum Event { + pub enum Event { EmergencyExecution: EmergencyExecution, Upgrade: Upgrade, TransferOwnership: TransferOwnership, @@ -128,32 +128,32 @@ mod ProtocolHandler { } #[derive(Drop, starknet::Event)] - struct EmergencyExecution { - call: Call + pub struct EmergencyExecution { + pub call: Call } #[derive(Drop, starknet::Event)] - struct Upgrade { - new_class_hash: ClassHash + pub struct Upgrade { + pub new_class_hash: ClassHash } #[derive(Drop, starknet::Event)] - struct TransferOwnership { - new_owner: ContractAddress + pub struct TransferOwnership { + pub new_owner: ContractAddress } #[derive(Drop, starknet::Event)] - struct SoftPause { - protocol_frozen_until: u64, + pub struct SoftPause { + pub protocol_frozen_until: u64, } #[derive(Drop, starknet::Event)] - struct HardPause { - protocol_frozen_until: u64, + pub struct HardPause { + pub protocol_frozen_until: u64, } #[derive(Drop, starknet::Event)] - struct Unpause {} + pub struct Unpause {} //* ------------------------------------------------------------------------ *// //* CONSTRUCTOR *// @@ -238,11 +238,11 @@ mod ProtocolHandler { self.protocol_frozen_until.write(protocol_frozen_until); // Call the Kakarot pause function - let kakarot = self.get_kakarot_dispatcher(); + let kakarot = self.kakarot.read(); kakarot.pause(); // Emit SoftPause event - self.emit(SoftPause { protocol_frozen_until: protocolFrozenUntil }); + self.emit(SoftPause { protocol_frozen_until: protocol_frozen_until }); } fn hard_pause(ref self: ContractState) { @@ -260,7 +260,7 @@ mod ProtocolHandler { kakarot.pause(); // Emit HardPause event - self.emit(HardPause { protocol_frozen_until: protocolFrozenUntil }); + self.emit(HardPause { protocol_frozen_until: protocol_frozen_until }); } fn unpause(ref self: ContractState) { @@ -281,4 +281,4 @@ mod ProtocolHandler { self.emit(Unpause {}); } } - +} diff --git a/cairo/protocol_handler/tests/test_protocol_handler.cairo b/cairo/protocol_handler/tests/test_protocol_handler.cairo index 8f982ebfa..6aac1670c 100644 --- a/cairo/protocol_handler/tests/test_protocol_handler.cairo +++ b/cairo/protocol_handler/tests/test_protocol_handler.cairo @@ -1,14 +1,11 @@ use snforge_std::{ ContractClassTrait, ContractClass, declare, DeclareResultTrait, EventSpyAssertionsTrait, - start_cheat_block_timestamp_global, start_cheat_caller_address, mock_call, spy_events, store, - load + start_cheat_block_timestamp_global, start_cheat_caller_address, mock_call, spy_events, store }; use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; use starknet::account::Call; -use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; -use starknet::Felt252TryIntoContractAddress; use protocol_handler::{ - IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, IProtocolHandler, ProtocolHandler + IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, ProtocolHandler }; fn setup_contracts_for_testing() -> ( @@ -305,7 +302,7 @@ fn test_protocol_handler_unpause_should_fail_wrong_caller_when_too_soon() { // Simulate pausing by writing in the storage // Find the storage address for the ProtocolFrozenUntil let mut state = ProtocolHandler::contract_state_for_testing(); - let storage_address = state.ProtocolFrozenUntil; + let storage_address = state.protocol_frozen_until; let value = (get_block_timestamp() * 10); let mut serialized_value: Array:: = array![]; Serde::serialize(@value, ref serialized_value); @@ -332,7 +329,7 @@ fn test_protocol_handler_unpause_should_pass_security_council() { // Simulate pausing by writing in the storage // Find the storage address for the ProtocolFrozenUntil let mut state = ProtocolHandler::contract_state_for_testing(); - let storage_address = state.ProtocolFrozenUntil; + let storage_address = state.protocol_frozen_until; let value = (get_block_timestamp() * 10); let mut serialized_value: Array:: = array![]; Serde::serialize(@value, ref serialized_value); @@ -374,7 +371,7 @@ fn test_protocol_handler_unpause_should_pass_after_delay() { // Simulate pausing by writing in the storage // Find the storage address for the ProtocolFrozenUntil let mut state = ProtocolHandler::contract_state_for_testing(); - let storage_address = state.ProtocolFrozenUntil; + let storage_address = state.protocol_frozen_until; let value = get_block_timestamp() - 1; let mut serialized_value: Array:: = array![]; Serde::serialize(@value, ref serialized_value); From 66cb1cce30764c163d699b6a338b690534ea66f0 Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 23 Oct 2024 16:14:00 +0200 Subject: [PATCH 5/9] define const as functions in test --- .../src/protocol_handler.cairo | 5 +- .../tests/test_protocol_handler.cairo | 109 +++++++++--------- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo index c8a0f0346..788877236 100644 --- a/cairo/protocol_handler/src/protocol_handler.cairo +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -57,10 +57,7 @@ pub mod ProtocolHandler { use starknet::event::EventEmitter; use starknet::account::Call; use starknet::{ContractAddress, ClassHash, get_block_timestamp, SyscallResultTrait}; - use starknet::storage::{ - Map, StoragePointerReadAccess, - StoragePointerWriteAccess - }; + use starknet::storage::{Map, StoragePointerReadAccess, StoragePointerWriteAccess}; use openzeppelin_access::accesscontrol::AccessControlComponent; use openzeppelin_introspection::src5::SRC5Component; use crate::kakarot_interface::{IKakarotDispatcher, IKakarotDispatcherTrait}; diff --git a/cairo/protocol_handler/tests/test_protocol_handler.cairo b/cairo/protocol_handler/tests/test_protocol_handler.cairo index 6aac1670c..24f46cde2 100644 --- a/cairo/protocol_handler/tests/test_protocol_handler.cairo +++ b/cairo/protocol_handler/tests/test_protocol_handler.cairo @@ -8,24 +8,31 @@ use protocol_handler::{ IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, ProtocolHandler }; -fn setup_contracts_for_testing() -> ( - IProtocolHandlerDispatcher, - ContractClass, - ContractAddress, - ContractAddress, - ContractAddress, - Span -) { - // Mock Kakarot, security council, operator and guardians - let kakarot_mock: ContractAddress = contract_address_const::<'kakarot_mock'>(); - let security_council_mock: ContractAddress = contract_address_const::< - 'security_council_mock' - >(); - let operator_mock: ContractAddress = contract_address_const::<'operator_mock'>(); - let guardians: Span = array![ +fn kakarot_mock() -> ContractAddress { + contract_address_const::<'security_council_mock'>() +} + +fn security_council_mock() -> ContractAddress { + contract_address_const::<'security_council_mock'>() +} + +fn operator_mock() -> ContractAddress { + contract_address_const::<'operator_mock'>() +} + +fn guardians_mock() -> Span { + array![ contract_address_const::<'guardian_mock_1'>(), contract_address_const::<'guardian_mock_2'>() ] - .span(); + .span() +} + +fn setup_contracts_for_testing() -> (IProtocolHandlerDispatcher, ContractClass) { + // Mock Kakarot, security council, operator and guardians + let kakarot_mock: ContractAddress = kakarot_mock(); + let security_council_mock: ContractAddress = security_council_mock(); + let operator_mock: ContractAddress = operator_mock(); + let guardians: Span = guardians_mock(); // Construct the calldata for the ProtocolHandler contrustor let mut constructor_calldata: Array:: = array![ @@ -38,32 +45,30 @@ fn setup_contracts_for_testing() -> ( // Get The dispatcher for the ProtocolHandler let protocol_handler = IProtocolHandlerDispatcher { contract_address }; - return ( - protocol_handler, *contract, kakarot_mock, security_council_mock, operator_mock, guardians - ); + return (protocol_handler, *contract); } #[test] #[should_panic(expected: 'Caller is missing role')] fn test_protocol_emergency_execution_fail_wrong_caller() { - let (protocol_handler, _, kakarot_mock, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to random caller address let random_caller = contract_address_const::<'random_caller'>(); start_cheat_caller_address(protocol_handler.contract_address, random_caller); // Call emergency_execution, should fail as caller is not security council - let call = Call { to: kakarot_mock, selector: 0, calldata: [].span() }; + let call = Call { to: kakarot_mock(), selector: 0, calldata: [].span() }; protocol_handler.emergency_execution(call); } #[test] #[should_panic(expected: 'ONLY_KAKAROT_CAN_BE_CALLED')] fn test_protocol_emergency_execution_fail_wrong_destination() { - let (protocol_handler, _, _, security_council_mock, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to security council - start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock()); // Construct the Call to a random address let random_called_address = contract_address_const::<'random_called_address'>(); @@ -75,14 +80,13 @@ fn test_protocol_emergency_execution_fail_wrong_destination() { #[test] fn test_protocol_emergency_execution_should_pass() { - let (protocol_handler, contract, kakarot_mock, security_council_mock, _, _) = - setup_contracts_for_testing(); + let (protocol_handler, contract) = setup_contracts_for_testing(); // Change caller to security council - start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock()); // Mock the call to Kakarot upgrade function - mock_call::<()>(kakarot_mock, selector!("upgrade"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("upgrade"), (), 1); // Construct the Call to protocol handler and call emergency_execution // Should pass as caller is security council and call is to Kakarot @@ -90,7 +94,7 @@ fn test_protocol_emergency_execution_should_pass() { let mut serialized_calldata: Array:: = array![]; Serde::serialize(@calldata, ref serialized_calldata); let call = Call { - to: kakarot_mock, selector: selector!("upgrade"), calldata: serialized_calldata.span() + to: kakarot_mock(), selector: selector!("upgrade"), calldata: serialized_calldata.span() }; // Spy on the events @@ -114,7 +118,7 @@ fn test_protocol_emergency_execution_should_pass() { #[test] #[should_panic(expected: 'Caller is missing role')] fn test_protocol_upgrade_fail_wrong_caller() { - let (protocol_handler, contract, _, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, contract) = setup_contracts_for_testing(); // Change caller to random caller address let random_caller = contract_address_const::<'random_caller'>(); @@ -125,14 +129,13 @@ fn test_protocol_upgrade_fail_wrong_caller() { } fn test_protocol_upgrade_should_pass() { - let (protocol_handler, contract, kakarot_mock, _, operator_mock, _) = - setup_contracts_for_testing(); + let (protocol_handler, contract) = setup_contracts_for_testing(); // Mock the call to Kakarot upgrade function - mock_call::<()>(kakarot_mock, selector!("upgrade"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("upgrade"), (), 1); // Change caller to security council - start_cheat_caller_address(protocol_handler.contract_address, operator_mock); + start_cheat_caller_address(protocol_handler.contract_address, operator_mock()); // Spy on the events let mut spy = spy_events(); @@ -157,7 +160,7 @@ fn test_protocol_upgrade_should_pass() { #[test] #[should_panic(expected: 'Caller is missing role')] fn test_protocol_handler_transfer_ownership_should_fail_wrong_caller() { - let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to not security council let not_security_council = contract_address_const::<'not_security_council'>(); @@ -170,14 +173,13 @@ fn test_protocol_handler_transfer_ownership_should_fail_wrong_caller() { #[test] fn test_protocol_handler_transfer_ownership_should_pass() { - let (protocol_handler, _, kakarot_mock, security_council_mock, _, _) = - setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to security council - start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock()); // Mock the call to Kakarot transfer_ownership - mock_call::<()>(kakarot_mock, selector!("transfer_ownership"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("transfer_ownership"), (), 1); // Spy on the events let mut spy = spy_events(); @@ -204,7 +206,7 @@ fn test_protocol_handler_transfer_ownership_should_pass() { #[test] #[should_panic(expected: 'Caller is missing role')] fn test_protocol_handler_soft_pause_should_fail_wrong_caller() { - let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to random caller address let random_caller = contract_address_const::<'random_caller'>(); @@ -216,12 +218,13 @@ fn test_protocol_handler_soft_pause_should_fail_wrong_caller() { #[test] fn test_protocol_handler_soft_pause_should_pass() { - let (protocol_handler, _, kakarot_mock, _, _, guardians) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Mock the call to kakarot pause function - mock_call::<()>(kakarot_mock, selector!("pause"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("pause"), (), 1); - // Change caller to guardian + // Change caller to a guardian + let guardians = guardians_mock(); start_cheat_caller_address(protocol_handler.contract_address, *guardians[0]); // Spy on the events @@ -249,7 +252,7 @@ fn test_protocol_handler_soft_pause_should_pass() { #[test] #[should_panic(expected: 'Caller is missing role')] fn test_protocol_handler_hard_pause_should_fail_wrong_caller() { - let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to not security council let not_security_council = contract_address_const::<'not_security_council'>(); @@ -261,14 +264,13 @@ fn test_protocol_handler_hard_pause_should_fail_wrong_caller() { #[test] fn test_protocol_handler_hard_pause_should_pass() { - let (protocol_handler, _, kakarot_mock, security_council_mock, _, _) = - setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Change caller to security council - start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock()); // Mock the call to kakarot pause function - mock_call::<()>(kakarot_mock, selector!("pause"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("pause"), (), 1); // Spy on the events let mut spy = spy_events(); @@ -297,7 +299,7 @@ fn test_protocol_handler_hard_pause_should_pass() { fn test_protocol_handler_unpause_should_fail_wrong_caller_when_too_soon() { // Block timestamp is 0 in tests, changing it to 10 start_cheat_block_timestamp_global(10); - let (protocol_handler, _, _, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Simulate pausing by writing in the storage // Find the storage address for the ProtocolFrozenUntil @@ -323,8 +325,7 @@ fn test_protocol_handler_unpause_should_fail_wrong_caller_when_too_soon() { fn test_protocol_handler_unpause_should_pass_security_council() { // Block timestamp is 0 in tests changing it to 10 start_cheat_block_timestamp_global(10); - let (protocol_handler, _, kakarot_mock, security_council_mock, _, _) = - setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Simulate pausing by writing in the storage // Find the storage address for the ProtocolFrozenUntil @@ -339,10 +340,10 @@ fn test_protocol_handler_unpause_should_pass_security_council() { ); // Change the caller to security council - start_cheat_caller_address(protocol_handler.contract_address, security_council_mock); + start_cheat_caller_address(protocol_handler.contract_address, security_council_mock()); // Mock call to kakarot unpause function - mock_call::<()>(kakarot_mock, selector!("unpause"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("unpause"), (), 1); // Spy on the events let mut spy = spy_events(); @@ -366,7 +367,7 @@ fn test_protocol_handler_unpause_should_pass_security_council() { fn test_protocol_handler_unpause_should_pass_after_delay() { // Block timestamp is 0 in tests start_cheat_block_timestamp_global(10); - let (protocol_handler, _, kakarot_mock, _, _, _) = setup_contracts_for_testing(); + let (protocol_handler, _) = setup_contracts_for_testing(); // Simulate pausing by writing in the storage // Find the storage address for the ProtocolFrozenUntil @@ -381,7 +382,7 @@ fn test_protocol_handler_unpause_should_pass_after_delay() { ); // Mock call to kakarot unpause function - mock_call::<()>(kakarot_mock, selector!("unpause"), (), 1); + mock_call::<()>(kakarot_mock(), selector!("unpause"), (), 1); // Spy on the events let mut spy = spy_events(); From ee0a5dd65a1f5eb401030e865ffa586239f7694e Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 23 Oct 2024 17:04:48 +0200 Subject: [PATCH 6/9] Use snforge_utils for call and event assertions --- .trunk/trunk.yaml | 1 + cairo/kakarot-ssj/Scarb.lock | 4 - cairo/kakarot-ssj/crates/contracts/Scarb.toml | 2 +- cairo/kakarot-ssj/crates/evm/Scarb.toml | 2 +- cairo/protocol_handler/Scarb.lock | 5 + cairo/protocol_handler/Scarb.toml | 1 + .../tests/test_protocol_handler.cairo | 156 +++++++++--------- .../crates => }/snforge_utils/.gitignore | 0 cairo/snforge_utils/Scarb.lock | 22 +++ .../crates => }/snforge_utils/Scarb.toml | 1 - .../snforge_utils/src/contracts.cairo | 0 .../crates => }/snforge_utils/src/lib.cairo | 18 -- 12 files changed, 109 insertions(+), 103 deletions(-) rename cairo/{kakarot-ssj/crates => }/snforge_utils/.gitignore (100%) create mode 100644 cairo/snforge_utils/Scarb.lock rename cairo/{kakarot-ssj/crates => }/snforge_utils/Scarb.toml (95%) rename cairo/{kakarot-ssj/crates => }/snforge_utils/src/contracts.cairo (100%) rename cairo/{kakarot-ssj/crates => }/snforge_utils/src/lib.cairo (93%) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index af5622922..4fda38950 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -74,6 +74,7 @@ lint: - cairo/utils - cairo/kakarot-ssj/crates - cairo/protocol_handler + - cairo/snforge_utils - linters: [ALL] paths: - logs* diff --git a/cairo/kakarot-ssj/Scarb.lock b/cairo/kakarot-ssj/Scarb.lock index 8a345708d..14508716b 100644 --- a/cairo/kakarot-ssj/Scarb.lock +++ b/cairo/kakarot-ssj/Scarb.lock @@ -59,10 +59,6 @@ dependencies = [ [[package]] name = "snforge_utils" version = "0.1.0" -dependencies = [ - "evm", - "snforge_std", -] [[package]] name = "utils" diff --git a/cairo/kakarot-ssj/crates/contracts/Scarb.toml b/cairo/kakarot-ssj/crates/contracts/Scarb.toml index 1e689c03e..f61afd9f3 100644 --- a/cairo/kakarot-ssj/crates/contracts/Scarb.toml +++ b/cairo/kakarot-ssj/crates/contracts/Scarb.toml @@ -25,7 +25,7 @@ name = "contracts" [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } assert_macros = "2.8.2" -snforge_utils = { path = "../snforge_utils" } +snforge_utils = { path = "../../../snforge_utils" } [scripts] test = "snforge test --max-n-steps 4294967295" diff --git a/cairo/kakarot-ssj/crates/evm/Scarb.toml b/cairo/kakarot-ssj/crates/evm/Scarb.toml index 1180c54a4..8ee6e0d06 100644 --- a/cairo/kakarot-ssj/crates/evm/Scarb.toml +++ b/cairo/kakarot-ssj/crates/evm/Scarb.toml @@ -14,7 +14,7 @@ garaga = { git = "https://github.com/keep-starknet-strange/garaga.git" } [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } -snforge_utils = { path = "../snforge_utils" } +snforge_utils = { path = "../../../snforge_utils" } assert_macros = "2.8.2" [tool] diff --git a/cairo/protocol_handler/Scarb.lock b/cairo/protocol_handler/Scarb.lock index b9a2215c3..36b152b8c 100644 --- a/cairo/protocol_handler/Scarb.lock +++ b/cairo/protocol_handler/Scarb.lock @@ -109,6 +109,7 @@ version = "0.1.0" dependencies = [ "openzeppelin", "snforge_std", + "snforge_utils", ] [[package]] @@ -123,3 +124,7 @@ source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72e dependencies = [ "snforge_scarb_plugin", ] + +[[package]] +name = "snforge_utils" +version = "0.1.0" diff --git a/cairo/protocol_handler/Scarb.toml b/cairo/protocol_handler/Scarb.toml index f9132441e..a1142ed13 100644 --- a/cairo/protocol_handler/Scarb.toml +++ b/cairo/protocol_handler/Scarb.toml @@ -11,6 +11,7 @@ openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", ta [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +snforge_utils = { path = "../snforge_utils" } [[target.starknet-contract]] casm = true diff --git a/cairo/protocol_handler/tests/test_protocol_handler.cairo b/cairo/protocol_handler/tests/test_protocol_handler.cairo index 24f46cde2..b52f93e75 100644 --- a/cairo/protocol_handler/tests/test_protocol_handler.cairo +++ b/cairo/protocol_handler/tests/test_protocol_handler.cairo @@ -1,13 +1,18 @@ use snforge_std::{ - ContractClassTrait, ContractClass, declare, DeclareResultTrait, EventSpyAssertionsTrait, + ContractClassTrait, ContractClass, declare, DeclareResultTrait, EventSpyTrait, start_cheat_block_timestamp_global, start_cheat_caller_address, mock_call, spy_events, store }; use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; use starknet::account::Call; +use starknet::class_hash::ClassHash; use protocol_handler::{ IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, ProtocolHandler }; +use snforge_utils::snforge_utils::{ + EventsFilterBuilderTrait, ContractEventsTrait, assert_called_with +}; + fn kakarot_mock() -> ContractAddress { contract_address_const::<'security_council_mock'>() } @@ -101,18 +106,17 @@ fn test_protocol_emergency_execution_should_pass() { let mut spy = spy_events(); protocol_handler.emergency_execution(call); + // Assert that upgrade was called on Kakarot + assert_called_with::(kakarot_mock(), selector!("upgrade"), contract.class_hash); + // Check the EmergencyExecution event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::EmergencyExecution( - ProtocolHandler::EmergencyExecution { call: call } - ) - ) - ] - ); + let expected = ProtocolHandler::Event::EmergencyExecution( + ProtocolHandler::EmergencyExecution { call: call } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } #[test] @@ -143,18 +147,17 @@ fn test_protocol_upgrade_should_pass() { // Call the protocol handler upgrade, should pass as caller is operator protocol_handler.upgrade(contract.class_hash); + // Assert that upgrade was called on Kakarot + assert_called_with::(kakarot_mock(), selector!("upgrade"), contract.class_hash); + // Check the TransferOwnership event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::Upgrade( - ProtocolHandler::Upgrade { new_class_hash: contract.class_hash } - ) - ) - ] - ); + let expected = ProtocolHandler::Event::Upgrade( + ProtocolHandler::Upgrade { new_class_hash: contract.class_hash } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } #[test] @@ -188,18 +191,19 @@ fn test_protocol_handler_transfer_ownership_should_pass() { let new_owner = contract_address_const::<'new_owner'>(); protocol_handler.transfer_ownership(new_owner); + // Assert that transfer_ownership was called on Kakarot + assert_called_with::< + ContractAddress + >(kakarot_mock(), selector!("transfer_ownership"), new_owner); + // Check the TransferOwnership event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::TransferOwnership( - ProtocolHandler::TransferOwnership { new_owner: new_owner } - ) - ) - ] - ); + let expected = ProtocolHandler::Event::TransferOwnership( + ProtocolHandler::TransferOwnership { new_owner: new_owner } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } @@ -233,20 +237,19 @@ fn test_protocol_handler_soft_pause_should_pass() { // Call the protocol handler soft_pause, should pass as caller is guardian protocol_handler.soft_pause(); + // Assert that pause was called on Kakarot + assert_called_with::<()>(kakarot_mock(), selector!("pause"), ()); + // Check the SoftPause event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::SoftPause( - ProtocolHandler::SoftPause { - protocol_frozen_until: ProtocolHandler::SOFT_PAUSE_DELAY // Blocktimestamp is 0 in tests - } - ) - ) - ] - ); + let expected = ProtocolHandler::Event::SoftPause( + ProtocolHandler::SoftPause { + protocol_frozen_until: ProtocolHandler::SOFT_PAUSE_DELAY // Blocktimestamp is 0 in tests + } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } #[test] @@ -278,20 +281,19 @@ fn test_protocol_handler_hard_pause_should_pass() { // Call the protocol handler hard_pause, should pass as caller is security council protocol_handler.hard_pause(); + // Assert that pause was called on Kakarot + assert_called_with::<()>(kakarot_mock(), selector!("pause"), ()); + // Check the HardPause event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::HardPause( - ProtocolHandler::HardPause { - protocol_frozen_until: ProtocolHandler::HARD_PAUSE_DELAY // Blocktimestamp is 0 in tests - } - ) - ) - ] - ); + let expected = ProtocolHandler::Event::HardPause( + ProtocolHandler::HardPause { + protocol_frozen_until: ProtocolHandler::HARD_PAUSE_DELAY // Blocktimestamp is 0 in tests + } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } #[test] @@ -351,16 +353,15 @@ fn test_protocol_handler_unpause_should_pass_security_council() { // Call the protocol handler unpause, should pass as caller is security council protocol_handler.unpause(); + // Assert that unpause was called on Kakarot + assert_called_with::<()>(kakarot_mock(), selector!("unpause"), ()); + // Check the Unpause event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::Unpause(ProtocolHandler::Unpause {}) - ) - ] - ); + let expected = ProtocolHandler::Event::Unpause(ProtocolHandler::Unpause {}); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } #[test] @@ -390,14 +391,13 @@ fn test_protocol_handler_unpause_should_pass_after_delay() { // Call the protocol handler unpause, should pass as caller is security council protocol_handler.unpause(); + // Assert that unpause was called on Kakarot + assert_called_with::<()>(kakarot_mock(), selector!("unpause"), ()); + // Check the Unpause event is emitted - spy - .assert_emitted( - @array![ - ( - protocol_handler.contract_address, - ProtocolHandler::Event::Unpause(ProtocolHandler::Unpause {}) - ) - ] - ); + let expected = ProtocolHandler::Event::Unpause(ProtocolHandler::Unpause {}); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); } diff --git a/cairo/kakarot-ssj/crates/snforge_utils/.gitignore b/cairo/snforge_utils/.gitignore similarity index 100% rename from cairo/kakarot-ssj/crates/snforge_utils/.gitignore rename to cairo/snforge_utils/.gitignore diff --git a/cairo/snforge_utils/Scarb.lock b/cairo/snforge_utils/Scarb.lock new file mode 100644 index 000000000..2c2ca5cc4 --- /dev/null +++ b/cairo/snforge_utils/Scarb.lock @@ -0,0 +1,22 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" + +[[package]] +name = "snforge_std" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" +dependencies = [ + "snforge_scarb_plugin", +] + +[[package]] +name = "snforge_utils" +version = "0.1.0" +dependencies = [ + "snforge_std", +] diff --git a/cairo/kakarot-ssj/crates/snforge_utils/Scarb.toml b/cairo/snforge_utils/Scarb.toml similarity index 95% rename from cairo/kakarot-ssj/crates/snforge_utils/Scarb.toml rename to cairo/snforge_utils/Scarb.toml index baa7e7add..2a1e55134 100644 --- a/cairo/kakarot-ssj/crates/snforge_utils/Scarb.toml +++ b/cairo/snforge_utils/Scarb.toml @@ -7,7 +7,6 @@ edition = "2024_07" [dependencies] starknet = "2.8.2" -evm = { path = "../evm" } [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } diff --git a/cairo/kakarot-ssj/crates/snforge_utils/src/contracts.cairo b/cairo/snforge_utils/src/contracts.cairo similarity index 100% rename from cairo/kakarot-ssj/crates/snforge_utils/src/contracts.cairo rename to cairo/snforge_utils/src/contracts.cairo diff --git a/cairo/kakarot-ssj/crates/snforge_utils/src/lib.cairo b/cairo/snforge_utils/src/lib.cairo similarity index 93% rename from cairo/kakarot-ssj/crates/snforge_utils/src/lib.cairo rename to cairo/snforge_utils/src/lib.cairo index eba213ef3..81fdb05be 100644 --- a/cairo/kakarot-ssj/crates/snforge_utils/src/lib.cairo +++ b/cairo/snforge_utils/src/lib.cairo @@ -4,10 +4,7 @@ mod contracts; pub mod snforge_utils { use core::array::ArrayTrait; use core::option::OptionTrait; - use evm::state::compute_storage_key; use starknet::ContractAddress; - use evm::model::Address; - use snforge_std::cheatcodes::storage::store_felt252; use snforge_std::Event; use snforge_std::cheatcodes::events::{Events}; use array_utils::ArrayExtTrait; @@ -301,19 +298,4 @@ pub mod snforge_utils { } } } - - /// Stores a value in the EVM storage of a given Starknet contract. - pub fn store_evm(target: Address, evm_key: u256, evm_value: u256) { - let storage_address = compute_storage_key(target.evm, evm_key); - let serialized_value = [evm_value.low.into(), evm_value.high.into()].span(); - for offset in 0 - ..serialized_value - .len() { - store_felt252( - target.starknet, - storage_address + offset.into(), - *serialized_value.at(offset) - ); - }; - } } From 2eba87d0ae0c26a7028edfb6fed37d8fedf44a76 Mon Sep 17 00:00:00 2001 From: Oba Date: Fri, 25 Oct 2024 12:37:11 +0200 Subject: [PATCH 7/9] protocol handler: mod errors --- cairo/protocol_handler/src/protocol_handler.cairo | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo index 788877236..fb2945f97 100644 --- a/cairo/protocol_handler/src/protocol_handler.cairo +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -89,6 +89,15 @@ pub mod ProtocolHandler { pub const SOFT_PAUSE_DELAY: u64 = 12 * 60 * 60; // 12 hours pub const HARD_PAUSE_DELAY: u64 = 7 * 24 * 60 * 60; // 7 days + + //* ------------------------------------------------------------------------ *// + //* ERRORS *// + //* ------------------------------------------------------------------------ *// + + pub mod errors { + pub const ONLY_KAKAROT_CAN_BE_CALLED: felt252 = 'ONLY_KAKAROT_CAN_BE_CALLED'; + } + //* ------------------------------------------------------------------------ *// //* STORAGE *// //* ------------------------------------------------------------------------ *// @@ -191,7 +200,7 @@ pub mod ProtocolHandler { // Check if the call is to the Kakarot contract let kakarot = self.kakarot.read().contract_address; let Call { to, selector, calldata } = call; - assert(to == kakarot, 'ONLY_KAKAROT_CAN_BE_CALLED'); + assert(to == kakarot, errors::ONLY_KAKAROT_CAN_BE_CALLED); // Call Kakarot with syscall starknet::syscalls::call_contract_syscall(to, selector, calldata).unwrap_syscall(); From d6563aeb3226d4bb0f7ab3950240e1cbd2bfbd5d Mon Sep 17 00:00:00 2001 From: Oba Date: Fri, 25 Oct 2024 12:45:11 +0200 Subject: [PATCH 8/9] protocol handler: check already paused soft_pause --- .../src/protocol_handler.cairo | 5 ++++ .../tests/test_protocol_handler.cairo | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo index fb2945f97..57002a88a 100644 --- a/cairo/protocol_handler/src/protocol_handler.cairo +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -96,6 +96,7 @@ pub mod ProtocolHandler { pub mod errors { pub const ONLY_KAKAROT_CAN_BE_CALLED: felt252 = 'ONLY_KAKAROT_CAN_BE_CALLED'; + pub const PROTOCOL_ALREADY_PAUSED: felt252 = 'PROTOCOL_ALREADY_PAUSED'; } //* ------------------------------------------------------------------------ *// @@ -237,6 +238,10 @@ pub mod ProtocolHandler { // Check only guardians can call self.accesscontrol.assert_only_role(GUARDIAN_ROLE); + // Check if the protocol is already paused + let protocol_frozen_until = self.protocol_frozen_until.read(); + assert(protocol_frozen_until == 0, errors::PROTOCOL_ALREADY_PAUSED); + // Cache the protocol frozen until timestamp let protocol_frozen_until = get_block_timestamp().into() + SOFT_PAUSE_DELAY; diff --git a/cairo/protocol_handler/tests/test_protocol_handler.cairo b/cairo/protocol_handler/tests/test_protocol_handler.cairo index b52f93e75..100121293 100644 --- a/cairo/protocol_handler/tests/test_protocol_handler.cairo +++ b/cairo/protocol_handler/tests/test_protocol_handler.cairo @@ -220,6 +220,31 @@ fn test_protocol_handler_soft_pause_should_fail_wrong_caller() { protocol_handler.soft_pause(); } +#[test] +#[should_panic(expected: 'PROTOCOL_ALREADY_PAUSED')] +fn test_protocol_handler_soft_pause_should_fail_already_paused() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + // Simulate pausing by writing in the storage + // Find the storage address for the ProtocolFrozenUntil + let mut state = ProtocolHandler::contract_state_for_testing(); + let storage_address = state.protocol_frozen_until; + let value = (get_block_timestamp() + 1); + let mut serialized_value: Array:: = array![]; + Serde::serialize(@value, ref serialized_value); + // Store the value in the storage of the protocol handler + store( + protocol_handler.contract_address, storage_address.__base_address__, serialized_value.span() + ); + + // Change caller to a guardian + let guardians = guardians_mock(); + start_cheat_caller_address(protocol_handler.contract_address, *guardians[0]); + + // Call the protocol handler soft_pause, should fail as protocol is already paused + protocol_handler.soft_pause(); +} + #[test] fn test_protocol_handler_soft_pause_should_pass() { let (protocol_handler, _) = setup_contracts_for_testing(); From b71dd04e41fc92883d24be5df23d89460546c4a8 Mon Sep 17 00:00:00 2001 From: Oba Date: Fri, 25 Oct 2024 15:10:00 +0200 Subject: [PATCH 9/9] feat: execute_call (#2) --- .../src/kakarot_interface.cairo | 26 +-- cairo/protocol_handler/src/lib.cairo | 3 +- .../src/protocol_handler.cairo | 103 ++++++++- .../tests/test_protocol_handler.cairo | 195 +++++++++++++++++- 4 files changed, 294 insertions(+), 33 deletions(-) diff --git a/cairo/protocol_handler/src/kakarot_interface.cairo b/cairo/protocol_handler/src/kakarot_interface.cairo index c4ab74dfa..1e66bdee6 100644 --- a/cairo/protocol_handler/src/kakarot_interface.cairo +++ b/cairo/protocol_handler/src/kakarot_interface.cairo @@ -2,33 +2,9 @@ use starknet::{ContractAddress, ClassHash}; #[starknet::interface] pub trait IKakarot { - //* ------------------------------------------------------------------------ *// - //* ADMIN FUNCTIONS *// - //* ------------------------------------------------------------------------ *// fn upgrade(ref self: TContractState, new_class_hash: ClassHash); fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); fn pause(ref self: TContractState); fn unpause(ref self: TContractState); - - //* ------------------------------------------------------------------------ *// - //* STORAGE SETTING FUNCTIONS *// - //* ------------------------------------------------------------------------ *// - fn set_native_token(ref self: TContractState, native_token: ContractAddress); - fn set_base_fee(ref self: TContractState, base_fee: u64); - fn set_coinbase(ref self: TContractState, new_coinbase: ContractAddress); - fn set_prev_randao(ref self: TContractState, pre_randao: felt252); - fn set_block_gas_limit(ref self: TContractState, new_block_gas_limit: felt252); - fn set_account_contract_class_hash(ref self: TContractState, new_class_hash: felt252); - fn set_uninitialized_account_class_hash(ref self: TContractState, new_class_hash: felt252); - fn set_authorized_cairo_precompile_caller( - ref self: TContractState, evm_address: ContractAddress, authorized: bool - ); - fn set_cairo1_helpers_class_hash(ref self: TContractState, new_class_hash: felt252); - fn upgrade_account(ref self: TContractState, evm_address: ContractAddress, new_class: felt252); - fn set_authorized_pre_eip155_tx( - ref self: TContractState, sender_address: ContractAddress, msg_hash: felt252 - ); - fn set_l1_messaging_contract_address( - ref self: TContractState, l1_messaging_contract_address: ContractAddress - ); + fn set_base_fee(ref self: TContractState, new_base_fee: felt252); } diff --git a/cairo/protocol_handler/src/lib.cairo b/cairo/protocol_handler/src/lib.cairo index f721ac330..2403b5483 100644 --- a/cairo/protocol_handler/src/lib.cairo +++ b/cairo/protocol_handler/src/lib.cairo @@ -1,5 +1,6 @@ mod protocol_handler; pub use protocol_handler::{ - ProtocolHandler, IProtocolHandler, IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait + ProtocolHandler, IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, + IProtocolHandlerSafeDispatcher, IProtocolHandlerSafeDispatcherTrait }; mod kakarot_interface; diff --git a/cairo/protocol_handler/src/protocol_handler.cairo b/cairo/protocol_handler/src/protocol_handler.cairo index 57002a88a..aea87296f 100644 --- a/cairo/protocol_handler/src/protocol_handler.cairo +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -50,6 +50,26 @@ pub trait IProtocolHandler { /// # Panics /// * `Caller is missing role` in case the caller is not the security council fn unpause(ref self: TContractState); + + /// Set the base fee of the Kakarot contract. + /// Only the gas price admin can call this function. + /// # Arguments + /// * `base_fee` - The new base fee + /// + /// # Panics + /// * `Caller is missing role` in case the caller is not the gas price admin + fn set_base_fee(ref self: TContractState, new_base_fee: felt252); + + /// Execute a call to the Kakarot contract + /// Only the operator can call this function. + /// # Arguments + /// * `call` - The call to be executed + /// + /// # Panics + /// * `Caller is missing role` in case the caller is not the operator + /// * `UNAUTHORIZED_SELECTOR` in case the selector is not authorized + /// * `ONLY_KAKAROT_CAN_BE_CALLED` in case the call is not to the Kakarot contract + fn execute_call(ref self: TContractState, call: Call); } #[starknet::contract] @@ -57,12 +77,14 @@ pub mod ProtocolHandler { use starknet::event::EventEmitter; use starknet::account::Call; use starknet::{ContractAddress, ClassHash, get_block_timestamp, SyscallResultTrait}; - use starknet::storage::{Map, StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::storage::{ + Map, StoragePointerReadAccess, StoragePointerWriteAccess, StorageMapReadAccess, + StorageMapWriteAccess + }; use openzeppelin_access::accesscontrol::AccessControlComponent; use openzeppelin_introspection::src5::SRC5Component; use crate::kakarot_interface::{IKakarotDispatcher, IKakarotDispatcherTrait}; - //* ------------------------------------------------------------------------ *// //* COMPONENTS *// //* ------------------------------------------------------------------------ *// @@ -83,8 +105,9 @@ pub mod ProtocolHandler { // Access controls roles const SECURITY_COUNCIL_ROLE: felt252 = selector!("SECURITY_COUNCIL_ROLE"); - const GUARDIAN_ROLE: felt252 = selector!("GUARDIAN_ROLE"); const OPERATOR_ROLE: felt252 = selector!("OPERATOR_ROLE"); + const GUARDIAN_ROLE: felt252 = selector!("GUARDIAN_ROLE"); + const GAS_PRICE_ADMIN_ROLE: felt252 = selector!("GAS_PRICE_ADMIN_ROLE"); // Pause delay pub const SOFT_PAUSE_DELAY: u64 = 12 * 60 * 60; // 12 hours pub const HARD_PAUSE_DELAY: u64 = 7 * 24 * 60 * 60; // 7 days @@ -97,6 +120,7 @@ pub mod ProtocolHandler { pub mod errors { pub const ONLY_KAKAROT_CAN_BE_CALLED: felt252 = 'ONLY_KAKAROT_CAN_BE_CALLED'; pub const PROTOCOL_ALREADY_PAUSED: felt252 = 'PROTOCOL_ALREADY_PAUSED'; + pub const UNAUTHORIZED_SELECTOR: felt252 = 'UNAUTHORIZED_SELECTOR'; } //* ------------------------------------------------------------------------ *// @@ -108,7 +132,9 @@ pub mod ProtocolHandler { pub kakarot: IKakarotDispatcher, pub operator: ContractAddress, pub guardians: Map, + pub gas_price_admin: ContractAddress, pub protocol_frozen_until: u64, + pub authorized_operator_selector: Map, #[substorage(v0)] accesscontrol: AccessControlComponent::Storage, #[substorage(v0)] @@ -128,6 +154,8 @@ pub mod ProtocolHandler { SoftPause: SoftPause, HardPause: HardPause, Unpause: Unpause, + BaseFeeChanged: BaseFeeChanged, + Execution: Execution, #[flat] AccessControlEvent: AccessControlComponent::Event, #[flat] @@ -162,6 +190,16 @@ pub mod ProtocolHandler { #[derive(Drop, starknet::Event)] pub struct Unpause {} + #[derive(Drop, starknet::Event)] + pub struct BaseFeeChanged { + pub new_base_fee: felt252 + } + + #[derive(Drop, starknet::Event)] + pub struct Execution { + pub call: Call + } + //* ------------------------------------------------------------------------ *// //* CONSTRUCTOR *// //* ------------------------------------------------------------------------ *// @@ -172,7 +210,8 @@ pub mod ProtocolHandler { kakarot: ContractAddress, security_council: ContractAddress, operator: ContractAddress, - mut guardians: Span, + gas_price_admin: ContractAddress, + mut guardians: Span ) { // Store the Kakarot address self.kakarot.write(IKakarotDispatcher { contract_address: kakarot }); @@ -186,6 +225,26 @@ pub mod ProtocolHandler { for guardian in guardians { self.accesscontrol._grant_role(GUARDIAN_ROLE, *guardian); }; + self.accesscontrol._grant_role(GAS_PRICE_ADMIN_ROLE, gas_price_admin); + + // Store the authorized selectors for the operator + self.authorized_operator_selector.write(selector!("set_native_token"), true); + self.authorized_operator_selector.write(selector!("set_coinbase"), true); + self.authorized_operator_selector.write(selector!("set_prev_randao"), true); + self.authorized_operator_selector.write(selector!("set_block_gas_limit"), true); + self.authorized_operator_selector.write(selector!("set_account_contract_class_hash"), true); + self + .authorized_operator_selector + .write(selector!("set_uninitialized_account_class_hash"), true); + self + .authorized_operator_selector + .write(selector!("set_authorized_cairo_precompile_caller"), true); + self.authorized_operator_selector.write(selector!("set_cairo1_helpers_class_hash"), true); + self.authorized_operator_selector.write(selector!("upgrade_account"), true); + self.authorized_operator_selector.write(selector!("set_authorized_pre_eip155_tx"), true); + self + .authorized_operator_selector + .write(selector!("set_l1_messaging_contract_address"), true); } #[abi(embed_v0)] @@ -291,5 +350,41 @@ pub mod ProtocolHandler { // Emit Unpause event self.emit(Unpause {}); } + + fn set_base_fee(ref self: ContractState, new_base_fee: felt252) { + // Check only gas price admin can call + self.accesscontrol.assert_only_role(GAS_PRICE_ADMIN_ROLE); + + // Call the Kakarot set_base_fee function + let kakarot = self.kakarot.read(); + kakarot.set_base_fee(new_base_fee); + + // Emit BaseFeeChanged + self.emit(BaseFeeChanged { new_base_fee }); + } + + //* ------------------------------------------------------------------------ *// + //* EXECUTE OPERATOR CALL *// + //* ------------------------------------------------------------------------ *// + + fn execute_call(ref self: ContractState, call: Call) { + // Check only operator can call + self.accesscontrol.assert_only_role(OPERATOR_ROLE); + + // Ensure the selector to call is part of the authorized selectors + let authorized = self.authorized_operator_selector.read(call.selector); + assert(authorized, errors::UNAUTHORIZED_SELECTOR); + + // Ensure the call is to the Kakarot + let kakarot = self.kakarot.read(); + assert(call.to == kakarot.contract_address, errors::ONLY_KAKAROT_CAN_BE_CALLED); + + // Call Kakarot with syscall + starknet::syscalls::call_contract_syscall(call.to, call.selector, call.calldata) + .unwrap_syscall(); + + // Emit Event Execution event + self.emit(Execution { call }); + } } } diff --git a/cairo/protocol_handler/tests/test_protocol_handler.cairo b/cairo/protocol_handler/tests/test_protocol_handler.cairo index 100121293..412e5f3f7 100644 --- a/cairo/protocol_handler/tests/test_protocol_handler.cairo +++ b/cairo/protocol_handler/tests/test_protocol_handler.cairo @@ -6,9 +6,9 @@ use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; use starknet::account::Call; use starknet::class_hash::ClassHash; use protocol_handler::{ - IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, ProtocolHandler + IProtocolHandlerDispatcher, IProtocolHandlerDispatcherTrait, ProtocolHandler, + IProtocolHandlerSafeDispatcher, IProtocolHandlerSafeDispatcherTrait }; - use snforge_utils::snforge_utils::{ EventsFilterBuilderTrait, ContractEventsTrait, assert_called_with }; @@ -32,18 +32,27 @@ fn guardians_mock() -> Span { .span() } +fn gas_price_admin_mock() -> ContractAddress { + contract_address_const::<'gas_price_admin_mock'>() +} + fn setup_contracts_for_testing() -> (IProtocolHandlerDispatcher, ContractClass) { // Mock Kakarot, security council, operator and guardians let kakarot_mock: ContractAddress = kakarot_mock(); let security_council_mock: ContractAddress = security_council_mock(); let operator_mock: ContractAddress = operator_mock(); + let gas_price_admin_mock: ContractAddress = gas_price_admin_mock(); let guardians: Span = guardians_mock(); // Construct the calldata for the ProtocolHandler contrustor let mut constructor_calldata: Array:: = array![ - kakarot_mock.into(), security_council_mock.into(), operator_mock.into() + kakarot_mock.into(), + security_council_mock.into(), + operator_mock.into(), + gas_price_admin_mock.into() ]; Serde::serialize(@guardians, ref constructor_calldata); + let contract = declare("ProtocolHandler").unwrap().contract_class(); let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); @@ -426,3 +435,183 @@ fn test_protocol_handler_unpause_should_pass_after_delay() { .build(); contract_events.assert_emitted(@expected); } + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_handler_execute_call_should_fail_wrong_caller() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + // Change caller to random caller address + let random_caller = contract_address_const::<'random_caller'>(); + start_cheat_caller_address(protocol_handler.contract_address, random_caller); + + // Call the protocol handler execute_call, should fail as caller is not operator + let call = Call { to: kakarot_mock(), selector: 0, calldata: [].span() }; + protocol_handler.execute_call(call); +} + +#[test] +#[should_panic(expected: 'UNAUTHORIZED_SELECTOR')] +fn test_protocol_handler_execute_call_should_fail_unauthorized_selector() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + // Change caller to operator + start_cheat_caller_address(protocol_handler.contract_address, operator_mock()); + + // Construct the Call to a random address + let random_called_address = contract_address_const::<'random_called_address'>(); + let call = Call { to: random_called_address, selector: 0, calldata: [].span() }; + + // Call the protocol handler execute_call, should fail as the selector is not authorized + protocol_handler.execute_call(call); +} + +#[test] +#[should_panic(expected: 'ONLY_KAKAROT_CAN_BE_CALLED')] +fn test_protocol_handler_execute_call_should_fail_wrong_destination() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + // Change caller to operator + start_cheat_caller_address(protocol_handler.contract_address, operator_mock()); + + // Construct the Call to kakarot + let random_called_address = contract_address_const::<'random_called_address'>(); + let call = Call { + to: random_called_address, selector: selector!("set_native_token"), calldata: [].span() + }; + + // Call the protocol handler execute_call, should fail as the call is not to Kakarot + protocol_handler.execute_call(call); +} + +#[test] +#[should_panic(expected: 'Caller is missing role')] +fn test_protocol_handler_set_base_fee_wrong_caller() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + // Change caller to random caller address + let random_caller = contract_address_const::<'random_caller'>(); + start_cheat_caller_address(protocol_handler.contract_address, random_caller); + + // Call the protocol handler set_base_fee, should fail as caller is not operator + protocol_handler.set_base_fee(0); +} + +#[test] +fn test_protocol_handler_set_base_fee_should_pass() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + // Change caller to gas price admin + start_cheat_caller_address(protocol_handler.contract_address, gas_price_admin_mock()); + + // Mock the call to Kakarot set_base_fee function + mock_call::<()>(kakarot_mock(), selector!("set_base_fee"), (), 1); + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler set_base_fee, should pass as caller is gas price admin + protocol_handler.set_base_fee(0); + + // Assert that unpause was called on Kakarot + assert_called_with::(kakarot_mock(), selector!("set_base_fee"), 0); + + // Check the BaseFeeChanged event is emitted + let expected = ProtocolHandler::Event::BaseFeeChanged( + ProtocolHandler::BaseFeeChanged { new_base_fee: 0 } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); +} + +#[test] +fn test_protocol_handler_execute_call_wrong_selector_should_fail() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + let unauthoried_selectors = [ + selector!("upgrade"), + selector!("transfer_ownership"), + selector!("pause"), + selector!("unpause"), + ]; + + // Change the caller to operator + start_cheat_caller_address(protocol_handler.contract_address, operator_mock()); + + // Get SafeDispatcher of protocolHandler + let safe_dispatcher = IProtocolHandlerSafeDispatcher { + contract_address: protocol_handler.contract_address + }; + + for selector in unauthoried_selectors + .span() { + // Mock the call to the Kakarot entrypoint + mock_call::<()>(kakarot_mock(), *selector, (), 1); + + // Construct the Call to protocol handler and call execute_call + // Should pass as caller is operator and call is to Kakarot + let call = Call { to: kakarot_mock(), selector: *selector, calldata: [].span() }; + + // Call the protocol handler execute_call + #[feature("safe_dispatcher")] + match safe_dispatcher.execute_call(call) { + Result::Ok(_) => panic!("Entrypoint did not panic"), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'UNAUTHORIZED_SELECTOR', *panic_data.at(0)); + } + }; + } +} + + +#[test] +fn test_protocol_handler_execute_call_should_pass() { + let (protocol_handler, _) = setup_contracts_for_testing(); + + let authorized_selectors = [ + selector!("set_native_token"), + selector!("set_coinbase"), + selector!("set_prev_randao"), + selector!("set_block_gas_limit"), + selector!("set_account_contract_class_hash"), + selector!("set_uninitialized_account_class_hash"), + selector!("set_authorized_cairo_precompile_caller"), + selector!("set_cairo1_helpers_class_hash"), + selector!("upgrade_account"), + selector!("set_authorized_pre_eip155_tx"), + selector!("set_l1_messaging_contract_address"), + ]; + + // Change caller to operator + start_cheat_caller_address(protocol_handler.contract_address, operator_mock()); + + for selector in authorized_selectors + .span() { + // Mock the call to Kakarot entrypoint + mock_call::<()>(kakarot_mock(), *selector, (), 1); + + // Construct the Call to protocol handler and call execute_call + // Should pass as caller is operator and call is to Kakarot + let call = Call { to: kakarot_mock(), selector: *selector, calldata: [].span() }; + + // Spy on the events + let mut spy = spy_events(); + + // Call the protocol handler execute_call + protocol_handler.execute_call(call); + + // Assert that selector was called on Kakarot + assert_called_with::<()>(kakarot_mock(), *selector, ()); + + // Check the ExecuteCall event is emitted + let expected = ProtocolHandler::Event::Execution( + ProtocolHandler::Execution { call: call } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(protocol_handler.contract_address) + .build(); + contract_events.assert_emitted(@expected); + } +}