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: eth_send_raw_transaction #1357

Merged
merged 19 commits into from
Aug 27, 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
91 changes: 6 additions & 85 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,94 +203,17 @@ 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
let (is_authorized) = Account_authorized_message_hashes.read(msg_hash);
if (pre_eip155_tx != FALSE) {
let (is_authorized) = Account_authorized_message_hashes.read(msg_hash);
with_attr error_message("Unauthorized pre-eip155 transaction") {
assert is_authorized = TRUE;
}
tempvar syscall_ptr = syscall_ptr;
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;
}
let syscall_ptr = cast([ap - 3], felt*);
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_unsigned_tx(
contract_address=kakarot_address, tx_data_len=tx_data_len, tx_data=tx_data
);

// See Argent account
Expand Down
106 changes: 103 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, assert_nn, 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,100 @@ func eth_send_transaction{

return result;
}

// @notice The eth_send_raw_unsigned_tx. Modified version of eth_sendRawTransaction function described in the spec.
// See https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction
// @dev This function takes the transaction data unsigned. Signature validation should be done before calling this function.
// @param tx_data_len The length of the unsigned transaction data
// @param tx_data The unsigned transaction data
// @return return_data_len The length of the return_data
// @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
obatirou marked this conversation as resolved.
Show resolved Hide resolved
func eth_send_raw_unsigned_tx{
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
) {
alloc_locals;
let tx = EthTransaction.decode(tx_data_len, tx_data);

// Validate chain_id for post eip155
let (chain_id) = Kakarot.eth_chain_id();
if (tx.chain_id.is_some != FALSE) {
with_attr error_message("Invalid chain id") {
assert tx.chain_id.value = chain_id;
}
}

// 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
with_attr error_message("Gas limit too high") {
assert [range_check_ptr] = tx.gas_limit;
let range_check_ptr = range_check_ptr + 1;
assert_le(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 (block_gas_limit) = Kakarot.get_block_gas_limit();
with_attr error_message("Transaction gas_limit > Block gas_limit") {
assert_nn(block_gas_limit - tx.gas_limit);
}

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

with_attr error_message("Max priority fee greater than max fee per gas") {
assert [range_check_ptr] = tx.max_priority_fee_per_gas;
let range_check_ptr = range_check_ptr + 1;
assert_le(tx.max_priority_fee_per_gas, tx.max_fee_per_gas);
}

let (evm_address) = IAccount.get_evm_address(caller_address);
let (balance) = eth_get_balance(evm_address);
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 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);
}
5 changes: 5 additions & 0 deletions src/kakarot/interfaces/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ namespace IKakarot {

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

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

@contract_interface
Expand Down
2 changes: 1 addition & 1 deletion src/kakarot/interpreter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ namespace Interpreter {

// 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()
// Note: balance > effective_fee was verified in eth_send_raw_unsigned_tx()
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_unsigned_tx,
)

// 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,
}
}
11 changes: 8 additions & 3 deletions src/utils/eth_transaction.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,19 @@ 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;
let chain_id = Helpers.bytes_to_felt(items[6].data_len, items[6].data);

tempvar is_some = 1;
tempvar chain_id = chain_id;
}
let is_some = [ap - 2];
obatirou marked this conversation as resolved.
Show resolved Hide resolved
let chain_id = [ap - 1];

tempvar tx = new model.EthTransaction(
Expand All @@ -78,7 +83,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 @@ -135,7 +140,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 @@ -193,7 +198,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
Loading