Skip to content

Commit

Permalink
feat: use execute_from_outside (#927)
Browse files Browse the repository at this point in the history
* feat: activate execute_from_outside

* fmt

* fix tests

* fmt

* remove todos

* apply pr suggestions

* fix tests
  • Loading branch information
enitrat authored Sep 12, 2024
1 parent f14fd32 commit eeac8bb
Show file tree
Hide file tree
Showing 16 changed files with 477 additions and 629 deletions.
166 changes: 38 additions & 128 deletions crates/contracts/src/account_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,27 @@ pub mod AccountContract {
use contracts::components::ownable::IOwnable;
use contracts::components::ownable::ownable_component::InternalTrait;
use contracts::components::ownable::ownable_component;
use contracts::errors::{KAKAROT_VALIDATION_FAILED, KAKAROT_REENTRANCY};
use contracts::errors::KAKAROT_REENTRANCY;
use contracts::kakarot_core::interface::{IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait};
use contracts::storage::StorageBytecode;
use core::cmp::min;
use core::num::traits::Bounded;
use core::num::traits::zero::Zero;
use core::panic_with_felt252;
use core::starknet::SyscallResultTrait;
use core::starknet::account::{Call};
use core::starknet::eth_signature::verify_eth_signature;
use core::starknet::storage::{
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
StoragePointerWriteAccess
};
use core::starknet::syscalls::{call_contract_syscall, replace_class_syscall};
use core::starknet::syscalls::call_contract_syscall;
use core::starknet::{
EthAddress, ClassHash, VALIDATED, get_caller_address, get_tx_info, get_block_timestamp
EthAddress, ClassHash, get_caller_address, get_tx_info, get_block_timestamp
};
use core::traits::TryInto;
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
use super::{IAccountLibraryDispatcher, IAccountDispatcherTrait, OutsideExecution};
use super::OutsideExecution;
use utils::constants::{POW_2_32};
use utils::eth_transaction::transaction::{TransactionUnsignedTrait, Transaction};
use utils::eth_transaction::validation::validate_eth_tx;
use utils::eth_transaction::{TransactionMetadata};
use utils::serialization::{deserialize_signature, deserialize_bytes, serialize_bytes};
use utils::traits::DefaultSignature;

Expand Down Expand Up @@ -164,95 +162,16 @@ pub mod AccountContract {

// EOA functions
fn __validate__(ref self: ContractState, calls: Array<Call>) -> felt252 {
let tx_info = get_tx_info().unbox();
assert(get_caller_address().is_zero(), 'EOA: reentrant call');
assert(calls.len() == 1, 'EOA: multicall not supported');
// todo: activate check once using snfoundry
// assert(tx_info.version.try_into().unwrap() >= 1_u128, 'EOA: deprecated tx version');
assert(self.Account_bytecode_len.read().is_zero(), 'EOAs: Cannot have code');
assert(tx_info.signature.len() == 5, 'EOA: invalid signature length');

let call = calls.at(0);
assert(*call.to == self.ownable.owner(), 'to is not kakarot core');
assert!(
*call.selector == selector!("eth_send_transaction"),
"Validate: selector must be eth_send_transaction"
);

let chain_id: u64 = tx_info.chain_id.try_into().unwrap() % POW_2_32.try_into().unwrap();
let signature = deserialize_signature(tx_info.signature, chain_id)
.expect('EOA: invalid signature');

let tx_metadata = TransactionMetadata {
address: self.Account_evm_address.read(),
chain_id,
account_nonce: tx_info.nonce.try_into().unwrap(),
signature
};

let mut encoded_tx = deserialize_bytes(*call.calldata)
.expect('conversion to Span<u8> failed')
.span();
let unsigned_transaction = TransactionUnsignedTrait::decode_enveloped(ref encoded_tx)
.expect('EOA: could not decode tx');
let validation_result = validate_eth_tx(tx_metadata, unsigned_transaction)
.expect('failed to validate eth tx');

assert(validation_result, 'transaction validation failed');

VALIDATED
panic!("EOA: __validate__ not supported")
}

/// Validate Declare is not used for Kakarot
fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
panic_with_felt252('Cannot Declare EOA')
panic!("EOA: declare not supported")
}

fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
let caller = get_caller_address();
let tx_info = get_tx_info().unbox();
assert(caller.is_zero(), 'EOA: reentrant call');
assert(calls.len() == 1, 'EOA: multicall not supported');
// todo: activate check once using snfoundry
// assert(tx_info.version.try_into().unwrap() >= 1_u128, 'EOA: deprecated tx version');

let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() };
let latest_class = kakarot.get_account_contract_class_hash();
let this_class = self.Account_implementation.read();

if (latest_class != this_class) {
self.Account_implementation.write(latest_class);
let response = IAccountLibraryDispatcher { class_hash: latest_class }
.__execute__(calls);
replace_class_syscall(latest_class).unwrap_syscall();
return response;
}

// Increment nonce to match protocol's nonce for EOAs.
self.Account_nonce.write(tx_info.nonce.try_into().unwrap() + 1);

let call: @Call = calls[0];

let mut encoded_tx = deserialize_bytes(*call.calldata)
.expect('conversion to Span<u8> failed')
.span();
let unsigned_transaction = TransactionUnsignedTrait::decode_enveloped(ref encoded_tx)
.expect('EOA: could not decode tx');

//TODO: validation of EIP-1559 transactions
// Not done because this endpoint will end up deprecated after EIP-1559
let is_valid = true;

let (success, return_data, gas_used) = if is_valid {
kakarot.eth_send_transaction(unsigned_transaction.transaction)
} else {
(false, KAKAROT_VALIDATION_FAILED.span(), 0)
};
let return_data = serialize_bytes(return_data).span();

self.emit(TransactionExecuted { response: return_data, success: success, gas_used });

array![return_data]
panic!("EOA: __execute__ not supported")
}

fn write_bytecode(ref self: ContractState, bytecode: Span<u8>) {
Expand Down Expand Up @@ -305,6 +224,7 @@ pub mod AccountContract {
let caller = get_caller_address();
let tx_info = get_tx_info();

// SNIP-9 Validation
if (outside_execution.caller.into() != 'ANY_CALLER') {
assert(caller == outside_execution.caller, 'SNIP9: Invalid caller');
}
Expand All @@ -313,58 +233,48 @@ pub mod AccountContract {
assert(block_timestamp > outside_execution.execute_after, 'SNIP9: Too early call');
assert(block_timestamp < outside_execution.execute_before, 'SNIP9: Too late call');

assert(outside_execution.calls.len() == 1, 'Multicall not supported');
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');

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"
);
// Kakarot-Specific Validation
assert(outside_execution.calls.len() == 1, 'KKRT: Multicall not supported');
assert(tx_info.version.into() >= 1_u256, 'KKRT: Deprecated tx version: 0');

// EOA Validation
assert(self.Account_bytecode_len.read().is_zero(), 'EOA: cannot have code');

let chain_id: u64 = tx_info.chain_id.try_into().unwrap() % POW_2_32.try_into().unwrap();
assert(signature.len() == 5, 'EOA: Invalid signature length');
let signature = deserialize_signature(signature, chain_id)
.expect('EOA: invalid signature');

let mut encoded_tx_data = deserialize_bytes((*outside_execution.calls[0]).calldata)
.expect('conversion to Span<u8> failed')
.span();
let unsigned_transaction = TransactionUnsignedTrait::decode_enveloped(
ref encoded_tx_data
)
.expect('EOA: could not decode tx');
// TODO(execute-from-outside): move validation to KakarotCore
let tx_metadata = TransactionMetadata {
address: self.Account_evm_address.read(),
chain_id,
account_nonce: self.Account_nonce.read().into(),
signature
};

let validation_result = validate_eth_tx(tx_metadata, unsigned_transaction)
.expect('failed to validate eth tx');

assert(validation_result, 'transaction validation failed');

//TODO: validate eip1559 transactions
// 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 is_valid = true;

let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() };
let address = self.Account_evm_address.read();
verify_eth_signature(unsigned_transaction.hash, signature, address);

let return_data = if is_valid {
let (_, return_data, _) = kakarot
.eth_send_transaction(unsigned_transaction.transaction);
return_data
} else {
KAKAROT_VALIDATION_FAILED.span()
};
let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() };
let (success, return_data, gas_used) = kakarot
.eth_send_transaction(unsigned_transaction.transaction);
let return_data = serialize_bytes(return_data).span();

// See Argent account
// https://github.com/argentlabs/argent-contracts-starknet/blob/1352198956f36fb35fa544c4e46a3507a3ec20e3/src/presets/user_account.cairo#L211-L213
// See 300 max data_len for events
// https://github.com/starkware-libs/blockifier/blob/9bfb3d4c8bf1b68a0c744d1249b32747c75a4d87/crates/blockifier/resources/versioned_constants.json
// The whole data_len should be less than 300, so it's the return_data should be less
// than 297 (+3 for return_data_len, success, gas_used)
self
.emit(
TransactionExecuted {
response: return_data.slice(0, min(297, return_data.len())),
success: success,
gas_used
}
);
array![return_data]
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/contracts/src/kakarot_core/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub trait IKakarotCore<TContractState> {

// Getter for the Base Fee
fn get_base_fee(self: @TContractState) -> u64;
/// Setter for the base fee
fn set_base_fee(ref self: TContractState, base_fee: u64);

// Getter for the Starknet Address
fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress;
Expand Down Expand Up @@ -113,6 +115,8 @@ pub trait IExtendedKakarotCore<TContractState> {
fn get_block_gas_limit(self: @TContractState) -> u64;
// Getter for the Base Fee
fn get_base_fee(self: @TContractState) -> u64;
/// Setter for the base fee
fn set_base_fee(ref self: TContractState, base_fee: u64);

// Getter for the Starknet Address
fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress;
Expand Down
18 changes: 12 additions & 6 deletions crates/contracts/src/kakarot_core/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod KakarotCore {
get_caller_address
};
use evm::backend::starknet_backend;
use evm::backend::validation::validate_eth_tx;
use evm::errors::{EVMError, ensure, EVMErrorTrait,};
use evm::gas;
use evm::model::account::AccountTrait;
Expand Down Expand Up @@ -158,20 +159,22 @@ pub mod KakarotCore {
let origin = Address { evm: origin, starknet: self.compute_starknet_address(origin) };

let TransactionResult { success, return_data, gas_used, state: _ } = self
.process_transaction(origin, tx);
.process_transaction(origin, tx, tx.effective_gas_price(Option::None));

(success, return_data, gas_used)
}

fn eth_send_transaction(ref self: ContractState, tx: Transaction) -> (bool, Span<u8>, u64) {
let gas_price = validate_eth_tx(@self, tx);

let starknet_caller_address = get_caller_address();
let account = IAccountDispatcher { contract_address: starknet_caller_address };
let origin = Address {
evm: account.get_evm_address(), starknet: starknet_caller_address
};

let TransactionResult { success, return_data, gas_used, mut state } = self
.process_transaction(origin, tx);
.process_transaction(origin, tx, gas_price);
starknet_backend::commit(ref state).expect('Committing state failed');
(success, return_data, gas_used)
}
Expand Down Expand Up @@ -219,6 +222,10 @@ pub mod KakarotCore {
self.Kakarot_block_gas_limit.read()
}

fn set_base_fee(ref self: ContractState, base_fee: u64) {
self.ownable.assert_only_owner();
self.Kakarot_base_fee.write(base_fee);
}

fn get_base_fee(self: @ContractState) -> u64 {
self.Kakarot_base_fee.read()
Expand Down Expand Up @@ -264,17 +271,17 @@ pub mod KakarotCore {


fn process_transaction(
self: @ContractState, origin: Address, tx: Transaction
self: @ContractState, origin: Address, tx: Transaction, gas_price: u128
) -> TransactionResult {
//TODO(gas) handle FeeMarketTransaction
let gas_price = tx.max_fee_per_gas();
let gas_limit = tx.gas_limit();
let mut env = starknet_backend::get_env(origin.evm, gas_price);

// TX Gas
let gas_fee = gas_limit.into() * gas_price;
let mut sender_account = env.state.get_account(origin.evm);
let sender_balance = sender_account.balance();
sender_account.set_nonce(sender_account.nonce() + 1);
env.state.set_account(sender_account);
match ensure(
sender_balance >= gas_fee.into() + tx.value(), EVMError::InsufficientBalance
) {
Expand Down Expand Up @@ -343,7 +350,6 @@ pub mod KakarotCore {
accessed_addresses: accessed_addresses.spanset(),
accessed_storage_keys: accessed_storage_keys.spanset(),
};

let mut summary = EVMTrait::process_message_call(message, env, is_deploy_tx);

// Gas refunds
Expand Down
8 changes: 6 additions & 2 deletions crates/contracts/src/test_data.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -1733,8 +1733,12 @@ pub fn storage_evm_bytecode() -> Span<u8> {
pub fn eip_2930_rlp_encoded_counter_inc_tx() -> Span<u8> {
[
1,
231,
1,
235,
132,
75,
75,
82,
84,
128,
132,
59,
Expand Down
19 changes: 11 additions & 8 deletions crates/contracts/src/test_utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDi
use snforge_std::start_cheat_chain_id_global;
use snforge_std::{
declare, DeclareResultTrait, start_cheat_caller_address, start_cheat_sequencer_address_global,
stop_cheat_caller_address, start_cheat_caller_address_global
stop_cheat_caller_address, start_cheat_caller_address_global, cheat_caller_address, CheatSpan
};
use utils::constants::BLOCK_GAS_LIMIT;
use utils::eth_transaction::legacy::TxLegacy;
Expand Down Expand Up @@ -80,12 +80,18 @@ pub fn deploy_kakarot_core(
(*kakarot_core_class_hash).into(), 0, calldata.span(), false
);

match maybe_address {
let kakarot_core = match maybe_address {
Result::Ok((
contract_address, _
)) => { IExtendedKakarotCoreDispatcher { contract_address } },
Result::Err(err) => panic(err)
}
};
cheat_caller_address(
kakarot_core.contract_address, other_starknet_address(), CheatSpan::TargetCalls(1)
);
kakarot_core.set_base_fee(1000);

kakarot_core
}

pub fn deploy_contract_account(
Expand Down Expand Up @@ -130,12 +136,9 @@ pub fn fund_account_with_native_token(
}

pub fn setup_contracts_for_testing() -> (IERC20CamelDispatcher, IExtendedKakarotCoreDispatcher) {
let native_token = deploy_native_token();
let kakarot_core = deploy_kakarot_core(
native_token.contract_address, [sequencer_evm_address()].span()
);

let sequencer: EthAddress = sequencer_evm_address();
let native_token = deploy_native_token();
let kakarot_core = deploy_kakarot_core(native_token.contract_address, [sequencer].span());

let sequencer_sn_address = kakarot_core.address_registry(sequencer);
start_cheat_sequencer_address_global(sequencer_sn_address);
Expand Down
5 changes: 1 addition & 4 deletions crates/contracts/tests/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
mod test_account_contract;
mod test_cairo1_helpers;
mod test_contract_account;


mod test_eoa;

mod test_execution_from_outside;

Expand Down
Loading

0 comments on commit eeac8bb

Please sign in to comment.