Skip to content
This repository was archived by the owner on Jan 9, 2025. It is now read-only.

feat: charge transaction gas to the sender #1025

Merged
merged 5 commits into from
Mar 12, 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
7 changes: 6 additions & 1 deletion scripts/utils/kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ async def _wrapper(self, *args, **kwargs):
logger.error(f"❌ {self.address}.{fun} failed")
raise EvmTransactionError(bytes(response))
logger.info(f"✅ {self.address}.{fun}")
return receipt
return {
"receipt": receipt,
"response": response,
"success": success,
"gas_used": gas_used,
}

return _wrapper

Expand Down
1 change: 1 addition & 0 deletions src/backend/starknet.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ namespace Starknet {

namespace Internals {
// @notice Iterate through the accounts dict and commit them
// @dev The dicts must've been squashed before calling this function
// @dev Account is deployed here if it doesn't exist already
// @param accounts_start The dict start pointer
// @param accounts_end The dict end pointer
Expand Down
57 changes: 52 additions & 5 deletions src/kakarot/interpreter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math_cmp import is_le, is_not_zero, is_nn
from starkware.cairo.common.math import split_felt
from starkware.cairo.lang.compiler.lib.registers import get_fp_and_pc
from starkware.cairo.common.uint256 import Uint256, uint256_le, uint256_sub
from starkware.cairo.common.uint256 import Uint256, uint256_le, uint256_sub, uint256_add
from starkware.cairo.common.math import unsigned_div_rem

// Internal dependencies
Expand Down Expand Up @@ -842,11 +842,13 @@ namespace Interpreter {

with state {
// Cache the coinbase, precompiles, caller, and target, making them warm
State.get_account(env.coinbase);
let coinbase = State.get_account(env.coinbase);
local coinbase_address: model.Address* = coinbase.address;
State.cache_precompiles();
State.get_account(address.evm);
let access_list_cost = State.cache_access_list(access_list_len, access_list);
let sender = State.get_account(env.origin);
local sender_address: model.Address* = sender.address;

// TODO: missing overflow checks on gas operations and values
let intrinsic_gas = intrinsic_gas + access_list_cost;
Expand All @@ -855,13 +857,15 @@ namespace Interpreter {
let is_gas_limit_enough = is_le(intrinsic_gas, gas_limit);
if (is_gas_limit_enough == FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
}

// TODO: same as below
let (is_value_le_balance) = uint256_le([value], [sender.balance]);
if (is_value_le_balance == FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
}
let (balance_post_value_transfer) = uint256_sub([sender.balance], [value]);
Expand All @@ -870,10 +874,11 @@ namespace Interpreter {
// tx.sender.balance this might be superfluous
let effective_gas_fee = gas_limit * env.gas_price;
let (fee_high, fee_low) = split_felt(effective_gas_fee);
let fee_u256 = Uint256(low=fee_low, high=fee_high);
let (can_pay_gasfee) = uint256_le(fee_u256, balance_post_value_transfer);
let max_fee_u256 = Uint256(low=fee_low, high=fee_high);
let (can_pay_gasfee) = uint256_le(max_fee_u256, balance_post_value_transfer);
if (can_pay_gasfee == FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
}

Expand All @@ -882,12 +887,13 @@ namespace Interpreter {
);
if (is_initcode_invalid != FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
}

// Charge the gas fee to the user without setting up a transfer.
// Transfers with the exact amounts will be performed post-execution.
let (local new_balance) = uint256_sub([sender.balance], fee_u256);
let (local new_balance) = uint256_sub([sender.balance], max_fee_u256);
let sender = Account.set_balance(sender, &new_balance);
State.update_account(sender);

Expand Down Expand Up @@ -931,6 +937,47 @@ namespace Interpreter {

let total_gas_used = gas_used - gas_refund;

with state {
// Reset the state if the execution has failed.
// Only the gas fee paid will be committed.
if (evm.reverted != 0) {
let state = State.init();

tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
tempvar state = state;
} else {
// Because we only "cached" for the local execution the sender's balance minus the maximum
// fee paid for the transaction, we need to restore the sender's balance to its original
// value to charge the actual fee proportional to the gas used.
let sender = State.get_account(env.origin);
let (local balance_pre_fee, _) = uint256_add([sender.balance], max_fee_u256);
let sender = Account.set_balance(sender, &balance_pre_fee);
State.update_account(sender);
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
tempvar state = state;
}
let syscall_ptr = cast([ap - 4], felt*);
let pedersen_ptr = cast([ap - 3], HashBuiltin*);
let range_check_ptr = [ap - 2];
let state = cast([ap - 1], model.State*);

// So as to not burn the base_fee_per gas, we send it to the coinbase.
let total_fee_charged = total_gas_used * env.gas_price;
let (fee_high, fee_low) = split_felt(total_fee_charged);
let fee_u256 = Uint256(low=fee_low, high=fee_high);

let transfer = model.Transfer(sender_address, coinbase.address, fee_u256);
// This should always succeed as we ensured the user has enough balance for value + gas_price * gas_limit
let success = State.add_transfer(transfer);

// State must be finalized as we added entries with the latest transfer
State.finalize();
}

return (evm, stack, memory, state, total_gas_used);
}

Expand Down
6 changes: 4 additions & 2 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,16 @@ func eth_send_transaction{
access_list_len,
access_list,
);

// Reverted or not - commit the state change. If reverted, the state was cleared to only contain gas-related changes.
Starknet.commit(state);

let is_reverted = is_not_zero(evm.reverted);
let result = (evm.return_data_len, evm.return_data, 1 - is_reverted, gas_used);

if (evm.reverted != FALSE) {
return result;
}

Starknet.commit(state);

return result;
}
29 changes: 14 additions & 15 deletions src/kakarot/state.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ namespace State {
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, state: model.State*
}(evm_address: felt) -> model.Account* {
alloc_locals;
let accounts = state.accounts;
local accounts: DictAccess* = state.accounts;
let (pointer) = dict_read{dict_ptr=accounts}(key=evm_address);

// Return from local storage if found
Expand All @@ -109,21 +109,20 @@ namespace State {
transfers=state.transfers,
);
return account;
} else {
// Otherwise read values from contract storage
local accounts: DictAccess* = accounts;
let account = Account.fetch_or_create(evm_address);
dict_write{dict_ptr=accounts}(key=evm_address, new_value=cast(account, felt));
tempvar state = new model.State(
accounts_start=state.accounts_start,
accounts=accounts,
events_len=state.events_len,
events=state.events,
transfers_len=state.transfers_len,
transfers=state.transfers,
);
return account;
}

// Otherwise read values from contract storage
let account = Account.fetch_or_create(evm_address);
dict_write{dict_ptr=accounts}(key=evm_address, new_value=cast(account, felt));
tempvar state = new model.State(
accounts_start=state.accounts_start,
accounts=accounts,
events_len=state.events_len,
events=state.events,
transfers_len=state.transfers_len,
transfers=state.transfers,
);
return account;
}

// @notice Cache precompiles accounts in the state, making them warm.
Expand Down
Loading
Loading