Skip to content

Commit

Permalink
feat: eth_send_raw_transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
obatirou committed Aug 21, 2024
1 parent 65e333a commit 6079706
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 299 deletions.
8 changes: 8 additions & 0 deletions src/kakarot/accounts/account_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ func set_code_hash{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_p
return ();
}

@external
func get_authorized_pre_eip155_tx{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
message_hash: Uint256
) -> (is_authorized: felt) {
let (is_authorized) = Account_authorized_message_hashes.read(message_hash);
return (is_authorized=is_authorized);
}

// @notice Authorizes a pre-eip155 transaction by message hash.
// @param message_hash The hash of the message.
@external
Expand Down
79 changes: 5 additions & 74 deletions src/kakarot/accounts/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.bool import FALSE, TRUE
from starkware.cairo.common.dict_access import DictAccess
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math import split_int, split_felt
from starkware.cairo.common.math import split_int
from starkware.cairo.common.memcpy import memcpy
from starkware.cairo.common.uint256 import Uint256, uint256_not, uint256_le
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.math_cmp import is_nn, is_le_felt
from starkware.cairo.common.math import assert_le_felt
from starkware.starknet.common.syscalls import (
StorageRead,
StorageWrite,
Expand All @@ -29,7 +28,6 @@ from kakarot.accounts.model import CallArray
from kakarot.errors import Errors
from kakarot.constants import Constants
from utils.eth_transaction import EthTransaction
from utils.uint256 import uint256_add
from utils.bytes import bytes_to_bytes8_little_endian
from utils.signature import Signature
from utils.utils import Helpers
Expand Down Expand Up @@ -205,9 +203,7 @@ namespace AccountContract {
helpers_class=helpers_class,
);

let tx = EthTransaction.decode(tx_data_len, tx_data);

// Whitelisting pre-eip155 or validate chain_id for post eip155
// Whitelisting pre-eip155
if (pre_eip155_tx != FALSE) {
let (is_authorized) = Account_authorized_message_hashes.read(msg_hash);
with_attr error_message("Unauthorized pre-eip155 transaction") {
Expand All @@ -217,9 +213,6 @@ namespace AccountContract {
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
} else {
with_attr error_message("Invalid chain id") {
assert tx.chain_id = chain_id;
}
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
Expand All @@ -228,71 +221,9 @@ namespace AccountContract {
let pedersen_ptr = cast([ap - 2], HashBuiltin*);
let range_check_ptr = [ap - 1];

// Validate nonce
let (account_nonce) = Account_nonce.read();
with_attr error_message("Invalid nonce") {
assert tx.signer_nonce = account_nonce;
}

// Validate gas and value
let (kakarot_address) = Ownable_owner.read();
let (native_token_address) = IKakarot.get_native_token(kakarot_address);
let (contract_address) = get_contract_address();
let (balance) = IERC20.balanceOf(native_token_address, contract_address);

with_attr error_message("Gas limit too high") {
assert_le_felt(tx.gas_limit, 2 ** 64 - 1);
}

with_attr error_message("Max fee per gas too high") {
assert [range_check_ptr] = tx.max_fee_per_gas;
let range_check_ptr = range_check_ptr + 1;
}

let max_gas_fee = tx.gas_limit * tx.max_fee_per_gas;
let (max_fee_high, max_fee_low) = split_felt(max_gas_fee);
let (tx_cost, carry) = uint256_add(tx.amount, Uint256(low=max_fee_low, high=max_fee_high));
assert carry = 0;
let (is_balance_enough) = uint256_le(tx_cost, balance);
with_attr error_message("Not enough ETH to pay msg.value + max gas fees") {
assert is_balance_enough = TRUE;
}

let (block_gas_limit) = IKakarot.get_block_gas_limit(kakarot_address);
let tx_gas_fits_in_block = is_nn(block_gas_limit - tx.gas_limit);
with_attr error_message("Transaction gas_limit > Block gas_limit") {
assert tx_gas_fits_in_block = TRUE;
}

let (block_base_fee) = IKakarot.get_base_fee(kakarot_address);
let enough_fee = is_nn(tx.max_fee_per_gas - block_base_fee);
with_attr error_message("Max fee per gas too low") {
assert enough_fee = TRUE;
}

with_attr error_message("Max priority fee greater than max fee per gas") {
assert_le_felt(tx.max_priority_fee_per_gas, tx.max_fee_per_gas);
}

let possible_priority_fee = tx.max_fee_per_gas - block_base_fee;
let priority_fee_is_max_priority_fee = is_nn(
possible_priority_fee - tx.max_priority_fee_per_gas
);
let priority_fee_per_gas = priority_fee_is_max_priority_fee * tx.max_priority_fee_per_gas +
(1 - priority_fee_is_max_priority_fee) * possible_priority_fee;
let effective_gas_price = priority_fee_per_gas + block_base_fee;

// Send tx to Kakarot
let (return_data_len, return_data, success, gas_used) = IKakarot.eth_send_transaction(
contract_address=kakarot_address,
to=tx.destination,
gas_limit=tx.gas_limit,
gas_price=effective_gas_price,
value=tx.amount,
data_len=tx.payload_len,
data=tx.payload,
access_list_len=tx.access_list_len,
access_list=tx.access_list,
let (return_data_len, return_data, success, gas_used) = IKakarot.eth_send_raw_transaction(
contract_address=kakarot_address, tx_data_len=tx_data_len, tx_data=tx_data
);

// See Argent account
Expand Down
99 changes: 96 additions & 3 deletions src/kakarot/eth_rpc.cairo
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
%lang starknet

from openzeppelin.access.ownable.library import Ownable_owner
from starkware.cairo.common.bool import FALSE, TRUE
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math_cmp import is_not_zero
from starkware.cairo.common.math import assert_le_felt, split_felt
from starkware.cairo.common.math_cmp import is_not_zero, is_nn
from starkware.cairo.common.registers import get_fp_and_pc
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.uint256 import Uint256, uint256_add, uint256_le
from starkware.starknet.common.syscalls import get_caller_address, get_tx_info

from backend.starknet import Starknet
Expand All @@ -12,6 +15,7 @@ from kakarot.interfaces.interfaces import IAccount, IERC20
from kakarot.library import Kakarot
from kakarot.model import model
from kakarot.storages import Kakarot_native_token_address
from utils.eth_transaction import EthTransaction
from utils.maths import unsigned_div_rem
from utils.utils import Helpers

Expand Down Expand Up @@ -180,7 +184,6 @@ func eth_estimate_gas{
// @return return_data An array of returned felts
// @return success An boolean, TRUE if the transaction succeeded, FALSE otherwise
// @return gas_used The amount of gas used by the transaction
@external
func eth_send_transaction{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(
Expand Down Expand Up @@ -221,3 +224,93 @@ func eth_send_transaction{

return result;
}

@external
func eth_send_raw_transaction{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(tx_data_len: felt, tx_data: felt*) -> (
return_data_len: felt, return_data: felt*, success: felt, gas_used: felt
) {
let tx = EthTransaction.decode(tx_data_len, tx_data);

// Whitelisting pre-eip155 or validate chain_id for post eip155
let (chain_id) = Kakarot.eth_chain_id();
tempvar is_tx_post_eip155 = is_not_zero(tx.chain_id.is_some);
if (is_tx_post_eip155 != FALSE) {
with_attr error_message("Invalid chain id") {
assert tx.chain_id.value = chain_id;
}
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
} else {
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
}
let syscall_ptr = cast([ap - 3], felt*);
let pedersen_ptr = cast([ap - 2], HashBuiltin*);
let range_check_ptr = [ap - 1];

// Get the caller address
let (caller_address) = get_caller_address();

// Validate nonce
let (account_nonce) = IAccount.get_nonce(contract_address=caller_address);
with_attr error_message("Invalid nonce") {
assert tx.signer_nonce = account_nonce;
}

// Validate gas and value
let (kakarot_address) = Ownable_owner.read();
let (native_token_address) = Kakarot.get_native_token();
let (balance) = IERC20.balanceOf(native_token_address, caller_address);

with_attr error_message("Gas limit too high") {
assert_le_felt(tx.gas_limit, 2 ** 64 - 1);
}

with_attr error_message("Max fee per gas too high") {
assert [range_check_ptr] = tx.max_fee_per_gas;
let range_check_ptr = range_check_ptr + 1;
}

Kakarot.get_block_gas_limit();
tempvar block_gas_limit = [[ap - 1]];
tempvar tx_gas_fits_in_block = is_nn(block_gas_limit - tx.gas_limit);
with_attr error_message("Transaction gas_limit > Block gas_limit") {
assert tx_gas_fits_in_block = TRUE;
}

let (block_base_fee) = Kakarot.get_base_fee();
let enough_fee = is_nn(tx.max_fee_per_gas - block_base_fee);
with_attr error_message("Max fee per gas too low") {
assert enough_fee = TRUE;
}

with_attr error_message("Max priority fee greater than max fee per gas") {
assert_le_felt(tx.max_priority_fee_per_gas, tx.max_fee_per_gas);
}

let possible_priority_fee = tx.max_fee_per_gas - block_base_fee;
let priority_fee_is_max_priority_fee = is_nn(
possible_priority_fee - tx.max_priority_fee_per_gas
);
let priority_fee_per_gas = priority_fee_is_max_priority_fee * tx.max_priority_fee_per_gas + (
1 - priority_fee_is_max_priority_fee
) * possible_priority_fee;
let effective_gas_price = priority_fee_per_gas + block_base_fee;

let (return_data_len, return_data, success, gas_used) = eth_send_transaction(
to=tx.destination,
gas_limit=tx.gas_limit,
gas_price=effective_gas_price,
value=tx.amount,
data_len=tx.payload_len,
data=tx.payload,
access_list_len=tx.access_list_len,
access_list=tx.access_list,
);

return (return_data_len, return_data, success, gas_used);
}
8 changes: 8 additions & 0 deletions src/kakarot/interfaces/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ namespace IAccount {
func write_jumpdests(jumpdests_len: felt, jumpdests: felt*) {
}

func get_authorized_pre_eip155_tx() -> (is_authorized: felt) {
}

func set_authorized_pre_eip155_tx(msg_hash: Uint256) {
}

Expand Down Expand Up @@ -189,6 +192,11 @@ namespace IKakarot {

func eth_chain_id() -> (chain_id: felt) {
}

func eth_send_raw_transaction(tx_data_len: felt, tx_data: felt*) -> (
return_data_len: felt, return_data: felt*, success: felt, gas_used: felt
) {
}
}

@contract_interface
Expand Down
14 changes: 11 additions & 3 deletions src/kakarot/interpreter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -895,9 +895,17 @@ namespace Interpreter {
return (evm, stack, memory, state, 0, 0);
}

// Charge the gas fee to the user without setting up a transfer.
// Transfers with the exact amounts will be performed post-execution.
// Note: balance > effective_fee was verified in AccountContract.execute_from_outside()
// // Charge the gas fee to the user without setting up a transfer.
// // Transfers with the exact amounts will be performed post-execution.
// // Note: balance > effective_fee was verified in AccountContract.execute_from_outside()
// let max_gas_fee = gas_limit * tx.max_fee_per_gas;
// let (max_fee_high, max_fee_low) = split_felt(max_gas_fee);
// let (tx_cost, carry) = uint256_add(tx.amount, Uint256(low=max_fee_low, high=max_fee_high));
// assert carry = 0;
// let (is_balance_enough) = uint256_le(tx_cost, balance);
// with_attr error_message("Not enough ETH to pay msg.value + max gas fees") {
// assert is_balance_enough = TRUE;
// }
let max_fee = gas_limit * env.gas_price;
let (fee_high, fee_low) = split_felt(max_fee);
let max_fee_u256 = Uint256(low=fee_low, high=fee_high);
Expand Down
1 change: 1 addition & 0 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ from kakarot.eth_rpc import (
eth_call,
eth_estimate_gas,
eth_send_transaction,
eth_send_raw_transaction,
)

// Constructor
Expand Down
2 changes: 1 addition & 1 deletion src/kakarot/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ namespace Kakarot {
block_gas_limit: felt
) {
let (block_gas_limit) = Kakarot_block_gas_limit.read();
return (block_gas_limit,);
return (block_gas_limit=block_gas_limit);
}

// @notice Deploy a new externally owned account.
Expand Down
2 changes: 1 addition & 1 deletion src/kakarot/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,6 @@ namespace model {
payload: felt*,
access_list_len: felt,
access_list: felt*,
chain_id: felt,
chain_id: Option,
}
}
10 changes: 7 additions & 3 deletions src/utils/eth_transaction.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@ namespace EthTransaction {

// pre eip-155 txs have 6 fields, post eip-155 txs have 9 fields
if (items_len == 6) {
tempvar is_some = 0;
tempvar chain_id = 0;
} else {
assert items_len = 9;
assert items[6].is_list = FALSE;
assert items[7].is_list = FALSE;
assert items[8].is_list = FALSE;
tempvar is_some = 1;
let chain_id = Helpers.bytes_to_felt(items[6].data_len, items[6].data);
tempvar chain_id = chain_id;
}
let is_some = [ap - 2];
let chain_id = [ap - 1];

tempvar tx = new model.EthTransaction(
Expand All @@ -77,7 +81,7 @@ namespace EthTransaction {
payload=payload,
access_list_len=0,
access_list=cast(0, felt*),
chain_id=chain_id,
chain_id=model.Option(is_some=is_some, value=chain_id),
);
return tx;
}
Expand Down Expand Up @@ -134,7 +138,7 @@ namespace EthTransaction {
payload=payload,
access_list_len=access_list_len,
access_list=access_list,
chain_id=chain_id,
chain_id=model.Option(is_some=1, value=chain_id),
);
return tx;
}
Expand Down Expand Up @@ -192,7 +196,7 @@ namespace EthTransaction {
payload=payload,
access_list_len=access_list_len,
access_list=access_list,
chain_id=chain_id,
chain_id=model.Option(is_some=1, value=chain_id),
);
return tx;
}
Expand Down
Loading

0 comments on commit 6079706

Please sign in to comment.