From b12229cd8fef866d544583352df584ab7400846c Mon Sep 17 00:00:00 2001 From: "remy.baranx@gmail.com" Date: Mon, 19 Aug 2024 20:09:45 +0200 Subject: [PATCH] feat: SNIP-9 --- crates/contracts/src/account_contract.cairo | 77 ++++++++++++++++++++- crates/utils/src/lib.cairo | 1 + crates/utils/src/snip9.cairo | 23 ++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 crates/utils/src/snip9.cairo diff --git a/crates/contracts/src/account_contract.cairo b/crates/contracts/src/account_contract.cairo index 39ff3356f..5c5b9d4a9 100644 --- a/crates/contracts/src/account_contract.cairo +++ b/crates/contracts/src/account_contract.cairo @@ -49,6 +49,7 @@ pub mod AccountContract { use core::panic_with_felt252; use core::starknet::SyscallResultTrait; use core::starknet::account::{Call}; + use core::starknet::secp256_trait::{Signature, signature_from_vrs}; use core::starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess @@ -57,7 +58,7 @@ pub mod AccountContract { use core::starknet::syscalls::{call_contract_syscall, replace_class_syscall}; use core::starknet::{ ContractAddress, EthAddress, ClassHash, VALIDATED, get_caller_address, get_contract_address, - get_tx_info, Store + get_tx_info, Store, get_block_timestamp }; use core::traits::TryInto; use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; @@ -69,6 +70,8 @@ pub mod AccountContract { use utils::helpers::{ByteArrayExTrait, ResultExTrait}; use utils::math::OverflowingMul; use utils::serialization::{deserialize_signature, deserialize_bytes, serialize_bytes}; + use utils::snip9::{IOutsideExecution, OutsideExecution}; + use utils::utils::load_packed_bytes; // Add ownable component component!(path: ownable_component, storage: ownable, event: OwnableEvent); @@ -291,6 +294,78 @@ pub mod AccountContract { } } + #[abi(embed_v0)] + impl OutsideExecutionImpl of IOutsideExecution { + fn execute_from_outside( + ref self: ContractState, outside_execution: OutsideExecution, signature: Span, + ) -> Array> { + let caller = get_caller_address(); + let tx_info = get_tx_info(); + + if (outside_execution.caller.into() != 'ANY_CALLER') { + assert(caller == outside_execution.caller, 'Invalid caller'); + } + + let block_timestamp = get_block_timestamp(); + assert(block_timestamp > outside_execution.execute_after, 'Too early call'); + assert(block_timestamp < outside_execution.execute_before, 'Too late call'); + + assert(self.Account_bytecode_len.read().is_zero(), 'EOAs cannot have code'); + assert(tx_info.version.into() >= 1_u256, 'Deprecated tx version'); + assert(signature.len() == 5, 'Invalid signature length'); + + assert(outside_execution.calls.len() == 1, 'Multicall not supported'); + + let call = outside_execution.calls.at(0); + assert(*call.to == self.ownable.owner(), 'to is not kakarot core'); + assert!( + *call.selector == selector!("eth_send_transaction"), + "selector must be eth_send_transaction" + ); + + let chain_id: u128 = tx_info.chain_id.try_into().unwrap() % POW_2_32; + + let signature = deserialize_signature(signature, chain_id).expect('invalid signature'); + + let eth_caller_address: felt252 = outside_execution.caller.into(); + + let tx_metadata = TransactionMetadata { + address: eth_caller_address.try_into().unwrap(), + chain_id, + account_nonce: outside_execution.nonce.into(), + signature + }; + + let encoded_tx = deserialize_bytes(*outside_execution.calls[0].calldata) + .expect('conversion to Span failed') + .span(); + + let validation_result = EthTransactionTrait::validate_eth_tx(tx_metadata, encoded_tx) + .expect('failed to validate eth tx'); + + assert(validation_result, 'transaction validation failed'); + + let tx = EthTransactionTrait::decode(encoded_tx).expect('rlp decoding of tx failed'); + + let is_valid = match tx.try_into_fee_market_transaction() { + Option::Some(tx_fee_infos) => { self.validate_eip1559_tx(@tx, tx_fee_infos) }, + Option::None => true + }; + + let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() }; + + let return_data = if is_valid { + let (_, return_data, _) = kakarot.eth_send_transaction(tx); + return_data + } else { + KAKAROT_VALIDATION_FAILED.span() + }; + let return_data = serialize_bytes(return_data).span(); + + array![return_data] + } + } + #[generate_trait] impl Eip1559TransactionImpl of Eip1559TransactionTrait { fn validate_eip1559_tx( diff --git a/crates/utils/src/lib.cairo b/crates/utils/src/lib.cairo index df98710c1..5c4a68c00 100644 --- a/crates/utils/src/lib.cairo +++ b/crates/utils/src/lib.cairo @@ -10,6 +10,7 @@ pub mod math; pub mod rlp; pub mod serialization; pub mod set; +pub mod snip9; mod test_data; diff --git a/crates/utils/src/snip9.cairo b/crates/utils/src/snip9.cairo new file mode 100644 index 000000000..5d84572d4 --- /dev/null +++ b/crates/utils/src/snip9.cairo @@ -0,0 +1,23 @@ +use core::starknet::ContractAddress; +use core::starknet::account::Call; + +#[derive(Copy, Drop, Serde)] +pub struct OutsideExecution { + pub caller: ContractAddress, + pub nonce: u64, + pub execute_after: u64, + pub execute_before: u64, + pub calls: Span +} + +#[starknet::interface] +pub trait IOutsideExecution { + /// @notice This method allows anyone to submit a transaction on behalf of the account as long + /// as they have the relevant signatures. + /// + /// @param `outside_execution` The parameters of the transaction to execute + /// @param `signature` a valid signature on the message encoding of `outside_execution` + fn execute_from_outside( + ref self: TContractState, outside_execution: OutsideExecution, signature: Span, + ) -> Array>; +}