diff --git a/CHANGELOG.md b/CHANGELOG.md index 362e33b698..7e6a0de054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New required flag `--type` to `account add` command +### Forge + +#### Changed + +- `L1HandlerTrait::execute()` takes source address and payloads as arguments [Read more here](https://foundry-rs.github.io/starknet-foundry/appendix/cheatcodes/l1_handler.html) + + ## [0.23.0] - 2024-05-08 ### Forge - #### Removed - `event_name_hash` removal, in favour of `selector!` usage diff --git a/crates/forge/tests/data/trace_resources/tests/test_l1_handler.cairo b/crates/forge/tests/data/trace_resources/tests/test_l1_handler.cairo index 4d75c00181..927671244e 100644 --- a/crates/forge/tests/data/trace_resources/tests/test_l1_handler.cairo +++ b/crates/forge/tests/data/trace_resources/tests/test_l1_handler.cairo @@ -15,8 +15,5 @@ fn test_l1_handler() { let mut l1_handler = L1HandlerTrait::new(checker_address, selector!("handle_l1_message")); - l1_handler.from_address = 123; - l1_handler.payload = array![proxy_address.into(), empty_hash.into(), 2].span(); - - l1_handler.execute().unwrap(); + l1_handler.execute(123, array![proxy_address.into(), empty_hash.into(), 2].span()).unwrap(); } diff --git a/crates/forge/tests/integration/gas.rs b/crates/forge/tests/integration/gas.rs index aac1380179..6a3cfd6879 100644 --- a/crates/forge/tests/integration/gas.rs +++ b/crates/forge/tests/integration/gas.rs @@ -675,10 +675,7 @@ fn l1_handler_cost() { let mut l1_handler = L1HandlerTrait::new(contract_address, selector!("handle_l1_message")); - l1_handler.from_address = 123; - l1_handler.payload = array![].span(); - - l1_handler.execute().unwrap(); + l1_handler.execute(123, array![].span()).unwrap(); } "# ), diff --git a/crates/forge/tests/integration/l1_handler_executor.rs b/crates/forge/tests/integration/l1_handler_executor.rs index cc871ec66a..e47a08a615 100644 --- a/crates/forge/tests/integration/l1_handler_executor.rs +++ b/crates/forge/tests/integration/l1_handler_executor.rs @@ -25,6 +25,7 @@ fn l1_handler_execute() { use array::{ArrayTrait, SpanTrait}; use core::result::ResultTrait; use snforge_std::{declare, ContractClassTrait, L1Handler, L1HandlerTrait}; + use snforge_std::errors::{ SyscallResultStringErrorTrait, PanicDataOrString }; #[test] fn l1_handler_execute() { @@ -46,10 +47,7 @@ fn l1_handler_execute() { selector!("process_l1_message") ); - l1_handler.from_address = 0x123; - l1_handler.payload = payload.span(); - - l1_handler.execute().unwrap(); + l1_handler.execute(0x123, payload.span()).unwrap(); let dispatcher = IBalanceTokenDispatcher { contract_address }; assert(dispatcher.get_balance() == 42, dispatcher.get_balance()); @@ -69,9 +67,7 @@ fn l1_handler_execute() { selector!("panicking_l1_handler") ); - l1_handler.from_address = 0x123; - l1_handler.payload = array![].span(); - match l1_handler.execute() { + match l1_handler.execute(0x123, array![].span()) { Result::Ok(_) => panic_with_felt252('should have panicked'), Result::Err(panic_data) => { assert(*panic_data.at(0) == 'custom', 'Wrong 1st panic datum'); @@ -79,6 +75,27 @@ fn l1_handler_execute() { }, } } + + #[test] + fn l1_handler_function_missing() { + let calldata = array![0x123]; + + let contract = declare("l1_handler_executor").unwrap(); + let (contract_address, _) = contract.deploy(@calldata).unwrap(); + + + let mut l1_handler = L1HandlerTrait::new( + contract_address, + selector!("this_does_not_exist") + ); + + match l1_handler.execute(0x123, array![].span()){ + Result::Ok(_) => panic_with_felt252('should have panicked'), + Result::Err(_) => { + // Would be nice to assert the error here once it is be possible in cairo + }, + } + } "# ), Contract::from_code_path( diff --git a/crates/forge/tests/integration/trace.rs b/crates/forge/tests/integration/trace.rs index 8774203032..cb91ea7cee 100644 --- a/crates/forge/tests/integration/trace.rs +++ b/crates/forge/tests/integration/trace.rs @@ -896,10 +896,7 @@ fn trace_l1_handler() { let mut l1_handler = L1HandlerTrait::new(checker_address, selector!("handle_l1_message")); - l1_handler.from_address = 123; - l1_handler.payload = array![proxy_address.into()].span(); - - l1_handler.execute().unwrap(); + l1_handler.execute(123, array![proxy_address.into()].span()).unwrap(); assert_trace(get_call_trace(), proxy_address, checker_address); } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 1ae817552d..bdeda7e8dc 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -66,7 +66,7 @@ * [sequencer_address](appendix/cheatcodes/sequencer_address.md) * [get_class_hash](appendix/cheatcodes/get_class_hash.md) * [replace_bytecode](appendix/cheatcodes/replace_bytecode.md) - * [l1_handler_execute](appendix/cheatcodes/l1_handler_execute.md) + * [l1_handler](appendix/cheatcodes/l1_handler.md) * [spy_events](appendix/cheatcodes/spy_events.md) * [store](appendix/cheatcodes/store.md) * [load](appendix/cheatcodes/load.md) diff --git a/docs/src/appendix/cheatcodes.md b/docs/src/appendix/cheatcodes.md index 9ad4613349..e121b7aa32 100644 --- a/docs/src/appendix/cheatcodes.md +++ b/docs/src/appendix/cheatcodes.md @@ -22,7 +22,7 @@ - [`stop_mock_call`](cheatcodes/mock_call.md#stop_mock_call) - cancels the `mock_call` / `start_mock_call` for an entry point - [`get_class_hash`](cheatcodes/get_class_hash.md) - retrieves a class hash of a contract - [`replace_bytecode`](cheatcodes/replace_bytecode.md) - replace the class hash of a contract -- [`l1_handler_execute`](cheatcodes/l1_handler_execute.md) - executes a `#[l1_handler]` function to mock a message arriving from Ethereum +- [`l1_handler`](cheatcodes/l1_handler.md) - executes a `#[l1_handler]` function to mock a message arriving from Ethereum - [`spy_events`](cheatcodes/spy_events.md) - creates `EventSpy` instance which spies on events emitted by contracts - [`store`](cheatcodes/store.md) - stores values in targeted contact's storage - [`load`](cheatcodes/load.md) - loads values directly from targeted contact's storage diff --git a/docs/src/appendix/cheatcodes/block_number.md b/docs/src/appendix/cheatcodes/block_number.md index 6433f2ac87..dc2a9c5376 100644 --- a/docs/src/appendix/cheatcodes/block_number.md +++ b/docs/src/appendix/cheatcodes/block_number.md @@ -15,4 +15,4 @@ Changes the block number for the given target. # `stop_roll` > `fn stop_roll(target: CheatTarget)` -Cancels the `roll` / `start_roll` for the given target. \ No newline at end of file +Cancels the `roll` / `start_roll` for the given target. diff --git a/docs/src/appendix/cheatcodes/l1_handler.md b/docs/src/appendix/cheatcodes/l1_handler.md new file mode 100644 index 0000000000..5e7c7e9376 --- /dev/null +++ b/docs/src/appendix/cheatcodes/l1_handler.md @@ -0,0 +1,7 @@ +# `l1_handler` + +> `fn new(target: ContractAddress, selector: felt252) -> L1Handler` +Returns a structure referring to a L1 handler function. + +> `fn execute(self: L1Handler) -> SyscallResult<()>` +Mocks a L1 -> L2 message from Ethereum handled by the given L1 handler function. diff --git a/docs/src/appendix/cheatcodes/l1_handler_execute.md b/docs/src/appendix/cheatcodes/l1_handler_execute.md deleted file mode 100644 index 189a5dcacd..0000000000 --- a/docs/src/appendix/cheatcodes/l1_handler_execute.md +++ /dev/null @@ -1,81 +0,0 @@ -# `l1_handler_execute` - -> `fn execute(self: L1Handler) -> SyscallResult<()>` - -Executes a `#[l1_handler]` function to mock a -[message](https://docs.starknet.io/documentation/architecture_and_concepts/L1-L2_Communication/messaging-mechanism/) -arriving from Ethereum. - -> 📝 **Note** -> -> Execution of the `#[l1_handler]` function may panic like any other function. -> It works like a regular `SafeDispatcher` would with a function call. -> For more info about asserting panic data check out [handling panic errors](../../testing/contracts.md#handling-errors) - - -```rust -struct L1Handler { - contract_address: ContractAddress, - function_selector: felt252, - from_address: felt252, - payload: Span::, -} -``` - -where: - -- `contract_address` - The target contract address -- `function_selector` - Selector of the `#[l1_handler]` function -- `from_address` - Ethereum address of the contract that sends the message -- `payload` - The message payload that may contain any Cairo data structure that can be serialized with -[Serde](https://book.cairo-lang.org/appendix-03-derivable-traits.html?highlight=serde#serializing-with-serde) - -It is important to note that when executing the `l1_handler`, -the `from_address` may be checked as any L1 contract can call any L2 contract. - -For a contract implementation: - -```rust -// ... -#[storage] -struct Storage { - l1_allowed: felt252, - //... -} - -//... - -#[l1_handler] -fn process_l1_message(ref self: ContractState, from_address: felt252, data: Span) { - assert(from_address == self.l1_allowed.read(), 'Unauthorized l1 contract'); -} -// ... -``` - -We can use `execute` method to test the execution of the `#[l1_handler]` function that is -not available through contracts dispatcher: - -```rust -use snforge_std::L1Handler; - -#[test] -fn test_l1_handler_execute() { - // ... - let data: Array = array![1, 2]; - - let mut payload_buffer: Array = ArrayTrait::new(); - // Note the serialization here. - data.serialize(ref payload_buffer); - - let mut l1_handler = L1HandlerTrait::new( - contract_address, - selector!("process_l1_message") - ); - - l1_handler.from_address = 0x123; - l1_handler.payload = payload.span(); - - l1_handler.execute().unwrap(); - //... -} -``` diff --git a/snforge_std/src/cheatcodes/l1_handler.cairo b/snforge_std/src/cheatcodes/l1_handler.cairo index e85e849f17..4c0a19f9cd 100644 --- a/snforge_std/src/cheatcodes/l1_handler.cairo +++ b/snforge_std/src/cheatcodes/l1_handler.cairo @@ -5,29 +5,37 @@ use super::super::_cheatcode::handle_cheatcode; #[derive(Drop, Clone)] struct L1Handler { - contract_address: ContractAddress, - function_selector: felt252, - from_address: felt252, - payload: Span::, + target: ContractAddress, + selector: felt252, } trait L1HandlerTrait { - fn new(contract_address: ContractAddress, function_selector: felt252) -> L1Handler; - fn execute(self: L1Handler) -> SyscallResult<()>; + fn new(target: ContractAddress, selector: felt252) -> L1Handler; + fn execute( + self: L1Handler, from_address: felt252, payload: Span:: + ) -> SyscallResult<()>; } impl L1HandlerImpl of L1HandlerTrait { - fn new(contract_address: ContractAddress, function_selector: felt252) -> L1Handler { - L1Handler { - contract_address, function_selector, from_address: 0, payload: array![].span(), - } + /// `target` - The target starknet contract address + /// `selector` - Selector of a `#[l1_handler]` function. Can be acquired with `selector!("function_handler_name")` macro + /// Returns a structure referring to a L1 handler function + fn new(target: ContractAddress, selector: felt252) -> L1Handler { + L1Handler { target, selector, } } - fn execute(self: L1Handler) -> SyscallResult<()> { + /// Mocks L1 -> L2 message from Ethereum handled by the given L1 handler function + /// `self` - `L1Handler` structure referring to a L1 handler function + /// `from_address` - Ethereum address of the contract that you want to be the message sender + /// `payload` - The handlers' function arguments serialized with `Serde` + /// Returns () or panic data if it failed + fn execute( + self: L1Handler, from_address: felt252, payload: Span:: + ) -> SyscallResult<()> { let mut inputs: Array:: = array![ - self.contract_address.into(), self.function_selector, self.from_address, + self.target.into(), self.selector, from_address.into(), ]; - self.payload.serialize(ref inputs); + payload.serialize(ref inputs); let mut outputs = handle_cheatcode(cheatcode::<'l1_handler_execute'>(inputs.span())); let exit_code = *outputs.pop_front().unwrap();