From 9d804a3093e113622a4193c2c2c932b73c61b25e Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 16 Oct 2024 14:18:33 +0200 Subject: [PATCH] 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 | 290 +++++++++++++ cairo/protocol_handler/tests/lib.cairo | 1 + .../tests/test_protocol_handler.cairo | 405 ++++++++++++++++++ 10 files changed, 886 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 35435011e..f5b9fd7bd 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..f33bc1875 --- /dev/null +++ b/cairo/protocol_handler/src/protocol_handler.cairo @@ -0,0 +1,290 @@ +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 Core 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 Core + 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 Core upgrade function + let kakarot_address = self.Kakarot.read(); + let kakarot = IKakarotDispatcher { contract_address: kakarot_address }; + 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 Core transfer_ownership function + let kakarot_address = self.Kakarot.read(); + let kakarot = IKakarotDispatcher { contract_address: kakarot_address }; + 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 Core pause function + let kakarot_address = self.Kakarot.read(); + let kakarot = IKakarotDispatcher { contract_address: kakarot_address }; + kakarot.pause(); + + // Emit SoftPause event + self.emit(SoftPause { protocol_frozen_until: self.ProtocolFrozenUntil.read() }); + } + + 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 Core pause function + let kakarot_address = self.Kakarot.read(); + let kakarot = IKakarotDispatcher { contract_address: kakarot_address }; + kakarot.pause(); + + // Emit HardPause event + self.emit(HardPause { protocol_frozen_until: self.ProtocolFrozenUntil.read() }); + } + + 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); + } + + // Unpause the protocol + // Call the Kakarot Core unpause function + let kakarot_address = self.Kakarot.read(); + let kakarot = IKakarotDispatcher { contract_address: kakarot_address }; + kakarot.unpause(); + + // Update storage + self.ProtocolFrozenUntil.write(0); + + // Emit Unpause event + self.emit(Unpause {}); + } + } +} 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 {}) + ) + ] + ); +}