Skip to content

Commit

Permalink
refactor: eth transaction model (#922)
Browse files Browse the repository at this point in the history
* refactor: eth transaction model

* lint

* TransactionUnsigned model

* lint

* fmt

* apply suggestions from review
  • Loading branch information
enitrat committed Sep 11, 2024
1 parent c8ba9f1 commit c66fe80
Show file tree
Hide file tree
Showing 46 changed files with 1,629 additions and 1,360 deletions.
159 changes: 81 additions & 78 deletions crates/contracts/src/account_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,17 @@ pub mod AccountContract {
};
use core::starknet::syscalls::{call_contract_syscall, replace_class_syscall};
use core::starknet::{
EthAddress, ClassHash, VALIDATED, get_caller_address, get_contract_address, get_tx_info,
get_block_timestamp
EthAddress, ClassHash, VALIDATED, 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 utils::constants::{POW_2_32};
use utils::eth_transaction::EthereumTransactionTrait;
use utils::eth_transaction::{EthTransactionTrait, TransactionMetadata};
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;

// Add ownable component
component!(path: ownable_component, storage: ownable, event: OwnableEvent);
Expand Down Expand Up @@ -109,7 +110,7 @@ pub mod AccountContract {
pub struct TransactionExecuted {
pub response: Span<felt252>,
pub success: bool,
pub gas_used: u128
pub gas_used: u64
}

#[constructor]
Expand Down Expand Up @@ -177,7 +178,7 @@ pub mod AccountContract {
"Validate: selector must be eth_send_transaction"
);

let chain_id: u128 = tx_info.chain_id.try_into().unwrap() % POW_2_32;
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');

Expand All @@ -188,11 +189,12 @@ pub mod AccountContract {
signature
};

let encoded_tx = deserialize_bytes(*call.calldata)
.expect('conversion to Span<u8> failed');
let validation_result = EthTransactionTrait::validate_eth_tx(
tx_metadata, encoded_tx.span()
)
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');
Expand Down Expand Up @@ -229,17 +231,19 @@ pub mod AccountContract {
self.Account_nonce.write(tx_info.nonce.try_into().unwrap() + 1);

let call: @Call = calls[0];
let encoded_tx = deserialize_bytes(*call.calldata).expect('conversion failed').span();

let tx = EthTransactionTrait::decode(encoded_tx).expect('rlp decoding of tx failed');
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 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
};
//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(tx)
kakarot.eth_send_transaction(unsigned_transaction.transaction)
} else {
(false, KAKAROT_VALIDATION_FAILED.span(), 0)
};
Expand Down Expand Up @@ -319,11 +323,16 @@ pub mod AccountContract {
*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 chain_id: u64 = tx_info.chain_id.try_into().unwrap() % POW_2_32.try_into().unwrap();
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(),
Expand All @@ -332,26 +341,23 @@ pub mod AccountContract {
signature
};

let encoded_tx = deserialize_bytes((*outside_execution.calls[0]).calldata)
.expect('conversion to Span<u8> failed')
.span();

let validation_result = EthTransactionTrait::validate_eth_tx(tx_metadata, encoded_tx)
let validation_result = validate_eth_tx(tx_metadata, unsigned_transaction)
.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
};
//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 return_data = if is_valid {
let (_, return_data, _) = kakarot.eth_send_transaction(tx);
let (_, return_data, _) = kakarot
.eth_send_transaction(unsigned_transaction.transaction);
return_data
} else {
KAKAROT_VALIDATION_FAILED.span()
Expand All @@ -364,49 +370,46 @@ pub mod AccountContract {

#[generate_trait]
impl Eip1559TransactionImpl of Eip1559TransactionTrait {
fn validate_eip1559_tx(
ref self: ContractState,
tx: @utils::eth_transaction::EthereumTransaction,
tx_fee_infos: utils::eth_transaction::FeeMarketTransaction
) -> bool {
let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() };
let block_gas_limit = kakarot.get_block_gas_limit();

if tx.gas_limit() >= block_gas_limit {
return false;
}

let base_fee = kakarot.get_base_fee();
let native_token = kakarot.get_native_token();
let balance = IERC20CamelDispatcher { contract_address: native_token }
.balanceOf(get_contract_address());

let max_fee_per_gas = tx_fee_infos.max_fee_per_gas;
let max_priority_fee_per_gas = tx_fee_infos.max_priority_fee_per_gas;

// ensure that the user was willing to at least pay the base fee
if base_fee >= max_fee_per_gas {
return false;
}

// ensure that the max priority fee per gas is not greater than the max fee per gas
if max_priority_fee_per_gas >= max_fee_per_gas {
return false;
}

let max_gas_fee = tx.gas_limit() * max_fee_per_gas;
let tx_cost = max_gas_fee.into() + tx_fee_infos.amount;

if tx_cost >= balance {
return false;
}

// priority fee is capped because the base fee is filled first
let possible_priority_fee = max_fee_per_gas - base_fee;

if max_priority_fee_per_gas >= possible_priority_fee {
return false;
}
//TODO: refactor into a generic tx validation function.
fn validate_eip1559_tx(ref self: ContractState, tx: Transaction,) -> bool {
// let kakarot = IKakarotCoreDispatcher { contract_address: self.ownable.owner() };
// let block_gas_limit = kakarot.get_block_gas_limit();

// if tx.gas_limit() >= block_gas_limit {
// return false;
// }

// let base_fee = kakarot.get_base_fee();
// let native_token = kakarot.get_native_token();
// let balance = IERC20CamelDispatcher { contract_address: native_token }
// .balanceOf(get_contract_address());

// let max_fee_per_gas = tx_fee_infos.max_fee_per_gas;
// let max_priority_fee_per_gas = tx_fee_infos.max_priority_fee_per_gas;

// // ensure that the user was willing to at least pay the base fee
// if base_fee >= max_fee_per_gas {
// return false;
// }

// // ensure that the max priority fee per gas is not greater than the max fee per gas
// if max_priority_fee_per_gas >= max_fee_per_gas {
// return false;
// }

// let max_gas_fee = tx.gas_limit() * max_fee_per_gas;
// let tx_cost = max_gas_fee.into() + tx_fee_infos.amount;

// if tx_cost >= balance {
// return false;
// }

// // priority fee is capped because the base fee is filled first
// let possible_priority_fee = max_fee_per_gas - base_fee;

// if max_priority_fee_per_gas >= possible_priority_fee {
// return false;
// }

return true;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/contracts/src/cairo1_helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub trait IPrecompiles<T> {
/// * A boolean indicating the success or failure of the execution.
/// * The gas cost of the execution.
/// * The output data from the execution.
fn exec_precompile(self: @T, address: felt252, data: Span<u8>) -> (bool, u128, Span<u8>);
fn exec_precompile(self: @T, address: felt252, data: Span<u8>) -> (bool, u64, Span<u8>);
}

#[starknet::interface]
Expand Down Expand Up @@ -134,7 +134,7 @@ pub mod embeddable_impls {
pub impl Precompiles<TContractState> of super::IPrecompiles<TContractState> {
fn exec_precompile(
self: @TContractState, address: felt252, data: Span<u8>
) -> (bool, u128, Span<u8>) {
) -> (bool, u64, Span<u8>) {
let result = match address {
0 => Result::Err(EVMError::NotImplemented),
1 => Result::Err(EVMError::NotImplemented),
Expand Down
25 changes: 12 additions & 13 deletions crates/contracts/src/kakarot_core/interface.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use core::starknet::{ContractAddress, EthAddress, ClassHash};
use utils::eth_transaction::EthereumTransaction;
use utils::eth_transaction::transaction::Transaction;

#[starknet::interface]
pub trait IKakarotCore<TContractState> {
Expand Down Expand Up @@ -28,14 +28,12 @@ pub trait IKakarotCore<TContractState> {
/// Performs view calls into the blockchain
/// It cannot modify the state of the chain
fn eth_call(
self: @TContractState, origin: EthAddress, tx: EthereumTransaction
) -> (bool, Span<u8>, u128);
self: @TContractState, origin: EthAddress, tx: Transaction
) -> (bool, Span<u8>, u64);

/// Transaction entrypoint into the EVM
/// Executes an EVM transaction and possibly modifies the state
fn eth_send_transaction(
ref self: TContractState, tx: EthereumTransaction
) -> (bool, Span<u8>, u128);
fn eth_send_transaction(ref self: TContractState, tx: Transaction) -> (bool, Span<u8>, u64);

/// Upgrade the KakarotCore smart contract
/// Using replace_class_syscall
Expand All @@ -53,9 +51,10 @@ pub trait IKakarotCore<TContractState> {
fn register_account(ref self: TContractState, evm_address: EthAddress);

// Getter for the Block Gas Limit
fn get_block_gas_limit(self: @TContractState) -> u128;
fn get_block_gas_limit(self: @TContractState) -> u64;

// Getter for the Base Fee
fn get_base_fee(self: @TContractState) -> u128;
fn get_base_fee(self: @TContractState) -> u64;

// Getter for the Starknet Address
fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress;
Expand Down Expand Up @@ -88,12 +87,12 @@ pub trait IExtendedKakarotCore<TContractState> {
/// Performs view calls into the blockchain
/// It cannot modify the state of the chain
fn eth_call(
self: @TContractState, origin: EthAddress, tx: EthereumTransaction
) -> (bool, Span<u8>);
self: @TContractState, origin: EthAddress, tx: Transaction
) -> (bool, Span<u8>, u64);

/// Transaction entrypoint into the EVM
/// Executes an EVM transaction and possibly modifies the state
fn eth_send_transaction(ref self: TContractState, tx: EthereumTransaction) -> (bool, Span<u8>);
fn eth_send_transaction(ref self: TContractState, tx: Transaction) -> (bool, Span<u8>, u64);

/// Upgrade the KakarotCore smart contract
/// Using replace_class_syscall
Expand All @@ -111,9 +110,9 @@ pub trait IExtendedKakarotCore<TContractState> {
fn register_account(ref self: TContractState, evm_address: EthAddress);

// Getter for the Block Gas Limit
fn get_block_gas_limit(self: @TContractState) -> u128;
fn get_block_gas_limit(self: @TContractState) -> u64;
// Getter for the Base Fee
fn get_base_fee(self: @TContractState) -> u128;
fn get_base_fee(self: @TContractState) -> u64;

// Getter for the Starknet Address
fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress;
Expand Down
Loading

0 comments on commit c66fe80

Please sign in to comment.