From c22e2e5f1760a8997ec015d4ebc69614592da0da Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 6 Feb 2025 13:38:53 +0100 Subject: [PATCH 1/4] Etherlink/Contracts: add a l2_caller parameter to entrypoints --- .../fast_withdrawal_mockup.mligo | 15 ++-- .../tezos_contracts/fast_withdrawal_mockup.tz | 57 ++++++++------ .../tezos_contracts/service_provider.mligo | 18 +++-- etherlink/tezos_contracts/service_provider.tz | 74 +++++++++++-------- 4 files changed, 99 insertions(+), 65 deletions(-) diff --git a/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo b/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo index 7912c338dab2..6711309f925b 100644 --- a/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo +++ b/etherlink/tezos_contracts/fast_withdrawal_mockup.mligo @@ -5,7 +5,7 @@ type storage = { exchanger: address; (* the address of the ticketer *) - withdrawals : (nat, (nat * timestamp * address * bytes)) big_map; (* stores (quantity, timestamp, payer_address, payload) for each withdrawal id *) + withdrawals : (nat, (nat * timestamp * address * bytes * bytes)) big_map; (* stores (quantity, timestamp, payer_address, payload, l2_caller) for each withdrawal id *) } type withdrawal_entry = { @@ -14,6 +14,7 @@ type withdrawal_entry = { timestamp : timestamp; base_withdrawer: address; payload: bytes; + l2_caller: bytes; } type payout_entry = { @@ -23,18 +24,19 @@ type payout_entry = { timestamp : timestamp; service_provider : address; payload: bytes; + l2_caller: bytes; } type return = operation list * storage [@entry] -let payout_withdrawal ({withdrawal_id; ticket; target; timestamp; service_provider; payload} : payout_entry) (storage: storage) : return = +let payout_withdrawal ({withdrawal_id; ticket; target; timestamp; service_provider; payload; l2_caller} : payout_entry) (storage: storage) : return = let is_in_storage = Option.is_some (Big_map.find_opt withdrawal_id storage.withdrawals) in (* Ensure that the fast withdrawal was not already payed. *) if not is_in_storage then (* Update storage to record prepayment. *) let (_, (_, amount)), ticket = Tezos.Next.Ticket.read ticket in - let updated_withdrawals = Big_map.add withdrawal_id (amount, timestamp, service_provider, payload) storage.withdrawals in + let updated_withdrawals = Big_map.add withdrawal_id (amount, timestamp, service_provider, payload, l2_caller) storage.withdrawals in let storage = { storage with withdrawals = updated_withdrawals } in (match Tezos.get_entrypoint_opt "%burn" storage.exchanger with | None -> failwith "Invalid tez ticket contract" @@ -44,7 +46,7 @@ let payout_withdrawal ({withdrawal_id; ticket; target; timestamp; service_provid failwith "The fast withdrawal was already payed" [@entry] -let default ({ withdrawal_id; ticket; timestamp; base_withdrawer; payload} : withdrawal_entry) (storage: storage) : return = +let default ({ withdrawal_id; ticket; timestamp; base_withdrawer; payload; l2_caller} : withdrawal_entry) (storage: storage) : return = match Big_map.find_opt withdrawal_id storage.withdrawals with | None -> (* No advance payment found, send to the withdrawer. *) @@ -52,10 +54,11 @@ let default ({ withdrawal_id; ticket; timestamp; base_withdrawer; payload} : wit | None -> failwith "Invalid tez ticket contract" | Some contract -> [Tezos.Next.Operation.transaction (base_withdrawer, ticket) 0mutez contract], storage) - | Some (prepaid_quantity, prepaid_timestamp, payer, stored_payload) -> + | Some (prepaid_quantity, prepaid_timestamp, payer, stored_payload, stored_l2_caller) -> (* Check if the provider has prepaid. *) let (_, (_, amount)), ticket = Tezos.Next.Ticket.read ticket in - if prepaid_timestamp = timestamp && prepaid_quantity = amount && stored_payload = payload then + if prepaid_timestamp = timestamp && prepaid_quantity = amount + && stored_payload = payload && stored_l2_caller = l2_caller then (* Everything matches, the withdrawal was payed, we send the amount to the payer. *) let updated_withdrawals = Big_map.remove withdrawal_id storage.withdrawals in diff --git a/etherlink/tezos_contracts/fast_withdrawal_mockup.tz b/etherlink/tezos_contracts/fast_withdrawal_mockup.tz index 35f9c19e8f94..3c01d18226e4 100644 --- a/etherlink/tezos_contracts/fast_withdrawal_mockup.tz +++ b/etherlink/tezos_contracts/fast_withdrawal_mockup.tz @@ -4,28 +4,31 @@ (ticket %ticket (pair nat (option bytes))) (timestamp %timestamp) (address %base_withdrawer) - (bytes %payload)) + (bytes %payload) + (bytes %l2_caller)) (pair %payout_withdrawal (nat %withdrawal_id) (ticket %ticket (pair nat (option bytes))) (address %target) (timestamp %timestamp) (address %service_provider) - (bytes %payload))) ; + (bytes %payload) + (bytes %l2_caller))) ; storage (pair (address %exchanger) - (big_map %withdrawals nat (pair nat timestamp address bytes))) ; + (big_map %withdrawals nat (pair nat timestamp address bytes bytes))) ; code { UNPAIR ; IF_LEFT - { UNPAIR 5 ; - DUP 6 ; + { UNPAIR 6 ; + DUP 7 ; CDR ; DUP 2 ; GET ; IF_NONE { DIG 2 ; DIG 4 ; - DROP 3 ; + DIG 5 ; + DROP 4 ; DUP 3 ; CAR ; CONTRACT %burn (pair address (ticket (pair nat (option bytes)))) ; @@ -43,29 +46,34 @@ PAIR } } { DIG 4 ; DROP ; - UNPAIR 4 ; - DIG 5 ; + UNPAIR 5 ; + DIG 6 ; READ_TICKET ; CDR ; CDR ; - DIG 8 ; - DIG 6 ; + DIG 10 ; + DIG 7 ; COMPARE ; EQ ; - SWAP ; - DIG 3 ; + DIG 9 ; + DIG 7 ; COMPARE ; EQ ; - DIG 6 ; + DIG 2 ; DIG 4 ; COMPARE ; EQ ; + DIG 7 ; + DIG 5 ; + COMPARE ; + EQ ; + AND ; AND ; AND ; IF { DUP 4 ; DIG 4 ; CDR ; - NONE (pair nat timestamp address bytes) ; + NONE (pair nat timestamp address bytes bytes) ; DIG 5 ; UPDATE ; UPDATE 2 ; @@ -85,8 +93,8 @@ CONS ; PAIR } } { DROP 4 ; PUSH string "Unexpected behavior" ; FAILWITH } } } - { UNPAIR 6 ; - DUP 7 ; + { UNPAIR 7 ; + DUP 8 ; CDR ; DUP 2 ; GET ; @@ -96,14 +104,15 @@ READ_TICKET ; CDR ; CDR ; - DUP 8 ; - DIG 8 ; + DUP 9 ; + DIG 9 ; CDR ; - DIG 8 ; - DIG 8 ; - DIG 8 ; - DIG 5 ; - PAIR 4 ; + DIG 9 ; + DIG 9 ; + DIG 9 ; + DIG 9 ; + DIG 6 ; + PAIR 5 ; SOME ; DIG 4 ; UPDATE ; @@ -123,5 +132,5 @@ TRANSFER_TOKENS ; CONS ; PAIR } } - { DROP 7 ; PUSH string "The fast withdrawal was already payed" ; FAILWITH } } } } + { DROP 8 ; PUSH string "The fast withdrawal was already payed" ; FAILWITH } } } } diff --git a/etherlink/tezos_contracts/service_provider.mligo b/etherlink/tezos_contracts/service_provider.mligo index 2c84f3e42c40..c6df302fb713 100644 --- a/etherlink/tezos_contracts/service_provider.mligo +++ b/etherlink/tezos_contracts/service_provider.mligo @@ -11,6 +11,7 @@ type storage = { timestamp : timestamp; service_provider : address; payload : bytes; + l2_caller : bytes; } type payout_entry = { @@ -21,6 +22,7 @@ type payout_entry = { timestamp : timestamp; service_provider : address; payload: bytes; + l2_caller: bytes; } type payout_entry_proxy = { @@ -31,25 +33,31 @@ type payout_entry_proxy = { timestamp : timestamp; service_provider : address; payload: bytes; + l2_caller : bytes; } type return = operation list * storage + + [@entry] -let payout_proxy ({fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload} : payout_entry_proxy) (_storage: storage) : return = +let payout_proxy ({fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload; l2_caller} : payout_entry_proxy) (_storage: storage) : return = + if not (Bytes.length l2_caller = 20n) then + failwith "L2 caller's address size should be 20 bytes long" + else let amount = Tezos.get_amount () in let payout = Tezos.address (Tezos.self("%payout"): tez_ticket contract) in match Tezos.get_entrypoint_opt "%mint" exchanger with | None -> failwith "Invalid tez ticket contract" | Some contract -> let mint = Tezos.Next.Operation.transaction payout amount contract in - let payout_storage = {fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload} in + let payout_storage = {fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload; l2_caller} in [mint], payout_storage [@entry] -let payout (ticket: tez_ticket) ({fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload}: storage) : return = +let payout (ticket: tez_ticket) ({fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload; l2_caller}: storage) : return = match Tezos.get_entrypoint_opt "%payout_withdrawal" fast_withdrawal_contract with | None -> failwith "Invalid entrypoint" | Some contract -> - let full_payload = (withdrawal_id, (ticket, (target, (timestamp, (service_provider, payload))))) in - [Tezos.Next.Operation.transaction full_payload 0mutez contract], {fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload} + let full_payload = (withdrawal_id, (ticket, (target, (timestamp, (service_provider, (payload, l2_caller)))))) in + [Tezos.Next.Operation.transaction full_payload 0mutez contract], {fast_withdrawal_contract; exchanger; withdrawal_id; target; timestamp; service_provider; payload; l2_caller} diff --git a/etherlink/tezos_contracts/service_provider.tz b/etherlink/tezos_contracts/service_provider.tz index 2ff300b4af09..df272e1878b2 100644 --- a/etherlink/tezos_contracts/service_provider.tz +++ b/etherlink/tezos_contracts/service_provider.tz @@ -7,7 +7,8 @@ (address %target) (timestamp %timestamp) (address %service_provider) - (bytes %payload))) ; + (bytes %payload) + (bytes %l2_caller))) ; storage (pair (address %fast_withdrawal_contract) (address %exchanger) @@ -15,28 +16,32 @@ (address %target) (timestamp %timestamp) (address %service_provider) - (bytes %payload)) ; + (bytes %payload) + (bytes %l2_caller)) ; code { UNPAIR ; IF_LEFT { SWAP ; - UNPAIR 7 ; + UNPAIR 8 ; DUP ; CONTRACT %payout_withdrawal - (pair nat (ticket (pair nat (option bytes))) address timestamp address bytes) ; + (pair nat (ticket (pair nat (option bytes))) address timestamp address bytes bytes) ; IF_NONE - { DROP 8 ; PUSH string "Invalid entrypoint" ; FAILWITH } - { DUP 8 ; - DUP 8 ; - DUP 8 ; - DUP 8 ; - DUP 8 ; - DIG 7 ; - DIG 7 ; - PAIR 7 ; + { DROP 9 ; PUSH string "Invalid entrypoint" ; FAILWITH } + { DUP 9 ; + DUP 9 ; + DUP 9 ; + DUP 9 ; + DUP 9 ; + DUP 9 ; + DIG 8 ; + DIG 8 ; + PAIR 8 ; NIL operation ; DIG 2 ; PUSH mutez 0 ; - DIG 8 ; + DIG 9 ; + DIG 9 ; + PAIR ; DIG 8 ; PAIR ; DIG 7 ; @@ -52,20 +57,29 @@ PAIR } } { SWAP ; DROP ; - UNPAIR 7 ; - AMOUNT ; - SELF %payout ; - ADDRESS ; - DUP 4 ; - CONTRACT %mint address ; - IF_NONE - { DROP 9 ; PUSH string "Invalid tez ticket contract" ; FAILWITH } - { DUG 2 ; - TRANSFER_TOKENS ; - DUG 7 ; - PAIR 7 ; - NIL operation ; - DIG 2 ; - CONS ; - PAIR } } } } + UNPAIR 8 ; + PUSH nat 20 ; + DUP 9 ; + SIZE ; + COMPARE ; + EQ ; + NOT ; + IF { DROP 8 ; + PUSH string "L2 caller's address size should be 20 bytes long" ; + FAILWITH } + { AMOUNT ; + SELF %payout ; + ADDRESS ; + DUP 4 ; + CONTRACT %mint address ; + IF_NONE + { DROP 10 ; PUSH string "Invalid tez ticket contract" ; FAILWITH } + { DUG 2 ; + TRANSFER_TOKENS ; + DUG 8 ; + PAIR 8 ; + NIL operation ; + DIG 2 ; + CONS ; + PAIR } } } } } -- GitLab From f95db6e8e05e0a8f9ada0385b1a1c6b551343511 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 6 Feb 2025 13:39:26 +0100 Subject: [PATCH 2/4] EVM/Kernel: change the interface of the entrypoint for FW --- etherlink/kernel_evm/evm_execution/src/handler.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etherlink/kernel_evm/evm_execution/src/handler.rs b/etherlink/kernel_evm/evm_execution/src/handler.rs index 04899ca99b53..34e2a51f3b20 100644 --- a/etherlink/kernel_evm/evm_execution/src/handler.rs +++ b/etherlink/kernel_evm/evm_execution/src/handler.rs @@ -65,13 +65,17 @@ pub type RouterInterface = MichelsonPair; /// * timestamp /// * withdrawer's address /// * generic payload +/// * l2 caller's address pub type FastWithdrawalInterface = MichelsonPair< MichelsonNat, MichelsonPair< FA2_1Ticket, MichelsonPair< MichelsonTimestamp, - MichelsonPair, + MichelsonPair< + MichelsonContract, + MichelsonPair, + >, >, >, >; -- GitLab From 38ee4604e65ab043793f4982ad63e4daaf983ad9 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 6 Feb 2025 13:40:00 +0100 Subject: [PATCH 3/4] EVM/Kernel: emit the l2 caller's address in the event and send it in the outbox message --- .../src/precompiles/withdrawal.rs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs b/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs index 73ef9f48d1a3..1adec42d0aaa 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs @@ -73,12 +73,12 @@ pub const WITHDRAWAL_EVENT_TOPIC: [u8; 32] = [ 102, 53, 63, 221, 233, 204, 248, 19, 244, 91, 132, 250, 130, ]; -/// Keccak256 of FastWithdrawal(bytes22,uint256,uint256,uint256,bytes) +/// Keccak256 of FastWithdrawal(bytes22,uint256,uint256,uint256,bytes,bytes20) /// Arguments in this order: l1 target address, withdrawal_id, amount, timestamp pub const FAST_WITHDRAWAL_EVENT_TOPIC: [u8; 32] = [ - 0xc3, 0x7b, 0x6b, 0x21, 0x84, 0x92, 0x3c, 0x72, 0xcb, 0xef, 0xf8, 0x85, 0x3f, 0x31, - 0x82, 0x14, 0xa8, 0xa1, 0x6d, 0xc1, 0xbe, 0xaa, 0x34, 0x82, 0x46, 0x41, 0xfe, 0x04, - 0xde, 0xa1, 0x69, 0x75, + 0xdf, 0x5e, 0xd9, 0xdc, 0x3d, 0x4e, 0x5e, 0x96, 0x98, 0x61, 0x60, 0xb2, 0x0d, 0xdc, + 0xb4, 0xde, 0x3b, 0x48, 0x9a, 0x63, 0xd0, 0x0d, 0x5b, 0xef, 0x21, 0x05, 0x11, 0xb7, + 0xc7, 0xb0, 0x70, 0xe0, ]; /// Calculate precompile gas cost given the estimated amount of ticks and gas price. @@ -440,6 +440,8 @@ pub fn withdrawal_precompile( let bytes_payload = MichelsonBytes(payload); + let caller = MichelsonBytes(context.caller.to_fixed_bytes().to_vec()); + let timestamp: MichelsonTimestamp = MichelsonTimestamp(Zarith(u256_to_bigint(timestamp_u256))); let contract_address = MichelsonContract(target.clone()); @@ -450,7 +452,10 @@ pub fn withdrawal_precompile( ticket, MichelsonPair( timestamp, - MichelsonPair(contract_address, bytes_payload), + MichelsonPair( + contract_address, + MichelsonPair(bytes_payload, caller), + ), ), ), ); @@ -475,6 +480,7 @@ pub fn withdrawal_precompile( ×tamp_u256, &target, &mut payload_event, + &context.caller, ); emit_log_and_return(handler, message, estimated_ticks, withdrawal_event) @@ -532,12 +538,14 @@ fn event_log( /// * `timestamp` - timestamp in milliseconds since Epoch /// * `receiver` - account on L1 that initiated the fast withdrawal /// * `payload` - generic payload +/// * `caller` - L2 caller's address fn event_log_fast_withdrawal( withdrawal_id: &U256, amount: &U256, timestamp: &U256, receiver: &Contract, payload: &mut Vec, + caller: &H160, ) -> Log { let mut data = vec![]; @@ -555,17 +563,25 @@ fn event_log_fast_withdrawal( data.extend_from_slice(&Into::<[u8; 32]>::into(*timestamp)); debug_assert!(data.len() % 32 == 0); - // 4*32 bytes of arguments (receiver, withdrawal_id, amount, timestamp) + // 5*32 bytes of arguments (receiver, withdrawal_id, amount, timestamp, caller) // + 1*32 bytes its position - let payload_position = U256::from(32 * 5); + let payload_position = U256::from(32 * 6); data.extend_from_slice(&Into::<[u8; 32]>::into(payload_position)); debug_assert!(data.len() % 32 == 0); + + let mut caller = caller.to_fixed_bytes().to_vec(); + caller.resize(32, 0); + data.extend_from_slice(&caller); + debug_assert!(data.len() % 32 == 0); + let payload_size = payload.len(); data.extend_from_slice(&Into::<[u8; 32]>::into(U256::from(payload_size))); debug_assert!(data.len() % 32 == 0); + let payload_padding = payload_size % 32; payload.resize(payload_size + payload_padding, 0); data.extend_from_slice(payload); + debug_assert!(data.len() % 32 == 0); Log { address: WITHDRAWAL_ADDRESS, @@ -641,8 +657,10 @@ mod tests { fn fast_withdrawal_event_signature() { assert_eq!( FAST_WITHDRAWAL_EVENT_TOPIC.to_vec(), - Keccak256::digest(b"FastWithdrawal(bytes22,uint256,uint256,uint256,bytes)") - .to_vec() + Keccak256::digest( + b"FastWithdrawal(bytes22,uint256,uint256,uint256,bytes,bytes20)" + ) + .to_vec() ); } -- GitLab From be101426a5aa241b054d622fe97ea96cb168ec7e Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Thu, 6 Feb 2025 13:40:13 +0100 Subject: [PATCH 4/4] Etherlink/Tezt: readapt the FW E2E tezt --- etherlink/tezt/tests/evm_rollup.ml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index 0c8f9b90b7b8..6bd21a4ffdf4 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -2658,7 +2658,7 @@ let test_deposit_and_fast_withdraw = "Pair \"KT1CeFqjJRJPNVvhvznQrWfHad2jCiDZ6Lyj\" \ \"KT1CeFqjJRJPNVvhvznQrWfHad2jCiDZ6Lyj\" 0 \ \"tz1etHLky7fuVumvBDi92ogXQZZPESiFimWR\" 0 \ - \"tz1etHLky7fuVumvBDi92ogXQZZPESiFimWR\" 0x00" + \"tz1etHLky7fuVumvBDi92ogXQZZPESiFimWR\" 0x00 0x00" ~prg:(service_provider_path ()) ~burn_cap:(Tez.of_int 890) client @@ -2671,7 +2671,7 @@ let test_deposit_and_fast_withdraw = in let fast_withdrawal_event_signature = - "FastWithdrawal(bytes22,uint256,uint256,uint256,bytes)" + "FastWithdrawal(bytes22,uint256,uint256,uint256,bytes,bytes20)" in (* Define the fast withdrawal event log topic, which will be searched for in the EVM logs. This topic is a hashed identifier that corresponds to the fast withdrawal transaction event. *) @@ -2696,9 +2696,8 @@ let test_deposit_and_fast_withdraw = (* Function to execute the payout by invoking the `payout_proxy` entrypoint on the L1 contract. The payout transfers funds to the service provider based on the withdrawal details. *) let execute_payout ~withdrawal_id ~fast_withdrawal_contract_address ~target - ~timestamp ~service_provider_proxy ~payload = + ~timestamp ~service_provider_proxy ~payload ~l2_caller = Client.transfer - ~force:true ~wait:"1" ~fee:(Tez.of_int 1) (* Small fee for the transaction *) ~fee_cap:(Tez.of_int 1) @@ -2712,14 +2711,15 @@ let test_deposit_and_fast_withdraw = ~entrypoint:"payout_proxy" ~arg: (Printf.sprintf - "(Pair %S %S %s %S %s %S %s)" + "(Pair %S %S %s %S %s %S %s %s)" fast_withdrawal_contract_address exchanger withdrawal_id target timestamp service_provider_pkh - payload) + payload + l2_caller) client in @@ -2746,7 +2746,7 @@ let test_deposit_and_fast_withdraw = () in let open Ezjsonm in - let _target, withdrawal_id, amount, timestamp, payload = + let _target, withdrawal_id, amount, timestamp, payload, l2_caller = let json = from_string res in match json with | `A @@ -2756,8 +2756,9 @@ let test_deposit_and_fast_withdraw = `String amount; `String timestamp; `String payload; + `String l2_caller; ] -> - (target, withdrawal_id, amount, timestamp, payload) + (target, withdrawal_id, amount, timestamp, payload, l2_caller) | _ -> assert false in assert (Wei.of_tez withdraw_amount = Wei.of_string amount) ; @@ -2771,6 +2772,7 @@ let test_deposit_and_fast_withdraw = ~target:withdraw_receiver ~timestamp ~payload + ~l2_caller in Lwt.return_unit in -- GitLab