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

refactor: eth transaction model #922

Merged
merged 6 commits into from
Sep 11, 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
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
Loading