Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use execute_from_outside #927

Merged
merged 7 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -163,95 +161,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 @@ -304,6 +223,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 @@ -312,58 +232,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');
obatirou marked this conversation as resolved.
Show resolved Hide resolved

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
Loading