Skip to content

Commit

Permalink
feat: eth_send_raw_transaction (#1357)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

<!-- Give an estimate of the time you spent on this PR in terms of work
days.
Did you spend 0.5 days on this PR or rather 2 days?  -->

Time spent on this PR:

## Pull request type

<!-- Please try to limit your pull request to one type,
submit multiple pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying,
or link to a relevant issue. -->

Resolves #1277

## What is the new behavior?
Add `eth_send_raw_transaction` entrypoint

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/kkrt-labs/kakarot/1357)
<!-- Reviewable:end -->

---------

Co-authored-by: Clément Walter <[email protected]>
  • Loading branch information
obatirou and ClementWalter committed Aug 27, 2024
1 parent ebcdf0b commit b3f8e18
Show file tree
Hide file tree
Showing 13 changed files with 339 additions and 389 deletions.
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
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];
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

0 comments on commit b3f8e18

Please sign in to comment.