Skip to content

Commit

Permalink
feat: activate execute_from_outside
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Sep 11, 2024
1 parent ce74fd0 commit 8f17f3e
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 134 deletions.
157 changes: 27 additions & 130 deletions crates/contracts/src/account_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub mod AccountContract {
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
Expand All @@ -73,6 +74,7 @@ pub mod AccountContract {
use utils::eth_transaction::validation::validate_eth_tx;
use utils::eth_transaction::{TransactionMetadata};
use utils::serialization::{deserialize_signature, deserialize_bytes, serialize_bytes};
use core::cmp::min;
use utils::traits::DefaultSignature;

// Add ownable component
Expand Down Expand Up @@ -163,106 +165,17 @@ 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 _encoded_tx_data = deserialize_bytes(*call.calldata)
.expect('conversion failed')
.span();

let _chain_id: u64 = tx_info
.chain_id
.try_into()
.unwrap() % POW_2_32
.try_into()
.unwrap();

//TODO: add a type for unsigned transaction
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");
array![]
}

fn write_bytecode(ref self: ContractState, bytecode: Span<u8>) {
Expand Down Expand Up @@ -315,6 +228,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 @@ -323,58 +237,41 @@ 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');
// 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 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: u64 = tx_info.chain_id.try_into().unwrap() % POW_2_32.try_into().unwrap();
assert(signature.len() == 5, '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
8 changes: 8 additions & 0 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 All @@ -27,9 +28,12 @@ pub mod KakarotCore {
use evm::precompiles::eth_precompile_addresses;
use evm::state::StateTrait;
use evm::{EVMTrait};
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
use utils::address::compute_contract_address;
use utils::constants::{POW_2_32};
use utils::eth_transaction::common::TxKind;
use utils::eth_transaction::eip2930::{AccessListItem, AccessListItemTrait};
use utils::eth_transaction::get_effective_gas_price;
use utils::eth_transaction::transaction::{Transaction, TransactionTrait};
use utils::helpers::compute_starknet_address;
use utils::set::{Set, SetTrait};
Expand Down Expand Up @@ -164,6 +168,8 @@ pub mod KakarotCore {
}

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

let starknet_caller_address = get_caller_address();
let account = IAccountDispatcher { contract_address: starknet_caller_address };
let origin = Address {
Expand Down Expand Up @@ -275,6 +281,8 @@ pub mod KakarotCore {
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
1 change: 1 addition & 0 deletions crates/evm/src/backend.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod starknet_backend;
pub mod validation;
59 changes: 59 additions & 0 deletions crates/evm/src/backend/validation.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use contracts::IKakarotCore;
use starknet::storage::StorageTrait;
use core::ops::SnapshotDeref;
use contracts::kakarot_core::KakarotCore;
use utils::eth_transaction::transaction::{Transaction, TransactionTrait};
use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait};
use core::starknet::{get_caller_address, get_tx_info};
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
use utils::constants::POW_2_32;
use core::starknet::storage::{StoragePointerReadAccess};
use utils::eth_transaction::get_effective_gas_price;

pub fn validate_eth_tx(kakarot_state: @KakarotCore::ContractState, tx: Transaction){
let kakarot_storage = kakarot_state.snapshot_deref().storage();
// Validate transaction

// Validate chain_id for post eip155
let tx_chain_id = tx.chain_id();
let kakarot_chain_id: u64 = get_tx_info()
.chain_id
.try_into()
.unwrap() % POW_2_32
.try_into()
.unwrap();
if (tx_chain_id.is_some()) {
assert(tx_chain_id.unwrap() == kakarot_chain_id, 'Invalid chain id');
}

// Validate nonce
let starknet_caller_address = get_caller_address();
let account = IAccountDispatcher { contract_address: starknet_caller_address };
assert(account.get_nonce() == tx.nonce(), 'Invalid nonce');

// Validate gas
assert(tx.gas_limit() <= kakarot_state.get_block_gas_limit(), 'Tx gas > Block gas');
let block_base_fee = kakarot_storage.Kakarot_base_fee.read();
assert(tx.max_fee_per_gas() <= block_base_fee.into(), 'Max fee per gas too low');
assert(
tx.max_priority_fee_per_gas().unwrap_or(0) <= tx.max_fee_per_gas(),
'Max prio fee > max fee per gas'
);

// Validate balance
let evm_address = account.get_evm_address();
let balance = IERC20CamelDispatcher {
contract_address: kakarot_storage.Kakarot_native_token_address.read()
}
.balanceOf(starknet_caller_address);
let max_gas_fee = tx.gas_limit().into() * tx.max_fee_per_gas();
let tx_cost = tx.value() + max_gas_fee.into();
assert(tx_cost <= balance, 'Not enough ETH');

let effective_gas_price = get_effective_gas_price(
Option::Some(tx.max_fee_per_gas()),
tx.max_priority_fee_per_gas(),
block_base_fee.into()
);
assert(effective_gas_price.is_ok(), 'Invalid effective gas price');
}
6 changes: 3 additions & 3 deletions crates/utils/src/eth_transaction.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ pub enum TransactTo {

/// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant
/// checks.
fn get_effective_gas_price(
max_fee_per_gas: Option<u256>, max_priority_fee_per_gas: Option<u256>, block_base_fee: u256,
) -> Result<u256, EthTransactionError> {
pub fn get_effective_gas_price(
max_fee_per_gas: Option<u128>, max_priority_fee_per_gas: Option<u128>, block_base_fee: u128,
) -> Result<u128, EthTransactionError> {
match max_fee_per_gas {
Option::Some(max_fee) => {
let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(0);
Expand Down
2 changes: 1 addition & 1 deletion crates/utils/src/serialization.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn serialize_transaction_signature(

let value = match tx_type {
TxType::Legacy(_) => { sig.y_parity.into() + 2 * chain_id + 35 },
TxType::Eip2930(_)| TxType::Eip1559(_) => { sig.y_parity.into() }
TxType::Eip2930(_) | TxType::Eip1559(_) => { sig.y_parity.into() }
};

res.append(value.into());
Expand Down

0 comments on commit 8f17f3e

Please sign in to comment.