Skip to content

Commit

Permalink
feat: estimate_gas (#1074)
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: 0.2d

## 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 #1067

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Adds an eth_estimate_gas entrypoint that returns the required gas for
the transaction to run successfully. This is different from gas_used,
s.t. `required gas >= gas used`, as the EVM can reimburse gas after its
execution.
-
-

<!-- 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/1074)
<!-- Reviewable:end -->
  • Loading branch information
enitrat authored Apr 5, 2024
1 parent fc4512d commit 3dcbada
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 15 deletions.
20 changes: 11 additions & 9 deletions src/kakarot/interpreter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,8 @@ namespace Interpreter {
// @return stack The stack post-execution
// @return memory The memory post-execution
// @return gas_used the gas used by the transaction
// @return required_gas The amount of gas required by the transaction to successfully execute. This is different
// from the gas used by the transaction as it doesn't take into account any refunds.
func execute{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
Expand All @@ -774,7 +776,7 @@ namespace Interpreter {
gas_limit: felt,
access_list_len: felt,
access_list: felt*,
) -> (model.EVM*, model.Stack*, model.Memory*, model.State*, felt) {
) -> (model.EVM*, model.Stack*, model.Memory*, model.State*, felt, felt) {
alloc_locals;
let fp_and_pc = get_fp_and_pc();
local __fp__: felt* = fp_and_pc.fp_val;
Expand Down Expand Up @@ -860,15 +862,15 @@ namespace Interpreter {
if (is_gas_limit_enough == FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
return (evm, stack, memory, state, 0, 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);
return (evm, stack, memory, state, 0, 0);
}
let (balance_post_value_transfer) = uint256_sub([sender.balance], [value]);

Expand All @@ -881,7 +883,7 @@ namespace Interpreter {
if (can_pay_gasfee == FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
return (evm, stack, memory, state, 0, 0);
}

tempvar is_initcode_invalid = is_deploy_tx * is_le(
Expand All @@ -890,7 +892,7 @@ namespace Interpreter {
if (is_initcode_invalid != FALSE) {
let evm = EVM.halt_validation_failed(evm);
State.finalize();
return (evm, stack, memory, state, 0);
return (evm, stack, memory, state, 0, 0);
}

// Charge the gas fee to the user without setting up a transfer.
Expand Down Expand Up @@ -931,14 +933,14 @@ namespace Interpreter {
let evm = run(evm);
}

let gas_used = gas_limit - evm.gas_left;
let (max_refund, _) = unsigned_div_rem(gas_used, 5);
let required_gas = gas_limit - evm.gas_left;
let (max_refund, _) = unsigned_div_rem(required_gas, 5);
let is_max_refund_le_gas_refund = is_le(max_refund, evm.gas_refund);
tempvar gas_refund = is_max_refund_le_gas_refund * max_refund + (
1 - is_max_refund_le_gas_refund
) * evm.gas_refund;

let total_gas_used = gas_used - gas_refund;
let total_gas_used = required_gas - gas_refund;

with state {
// Reset the state if the execution has failed.
Expand Down Expand Up @@ -981,7 +983,7 @@ namespace Interpreter {
State.finalize();
}

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

// @notice A placeholder for opcodes that don't exist
Expand Down
53 changes: 51 additions & 2 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func eth_call{
alloc_locals;
let fp_and_pc = get_fp_and_pc();
local __fp__: felt* = fp_and_pc.fp_val;
let (evm, state, gas_used) = Kakarot.eth_call(
let (evm, state, gas_used, _) = Kakarot.eth_call(
nonce,
origin,
to,
Expand All @@ -224,6 +224,55 @@ func eth_call{
return (evm.return_data_len, evm.return_data, 1 - is_reverted, gas_used);
}

// @notice The eth_estimateGas function as described in the spec,
// see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
// This is a view only function, meaning that it doesn't make any state change.
// @param origin The address the transaction is sent from.
// @param to The address the transaction is directed to.
// @param gas_limit Integer of the gas provided for the transaction execution
// @param gas_price Integer of the gas price used for each paid gas
// @param value Integer of the value sent with this transaction
// @param data_len The length of the data
// @param data Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation
// @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 required_gas The amount of gas required by the transaction to successfully execute. This is different
// from the gas used by the transaction as it doesn't take into account any refunds.
@view
func eth_estimate_gas{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(
nonce: felt,
origin: felt,
to: model.Option,
gas_limit: felt,
gas_price: felt,
value: Uint256,
data_len: felt,
data: felt*,
access_list_len: felt,
access_list: felt*,
) -> (return_data_len: felt, return_data: felt*, success: felt, required_gas: felt) {
alloc_locals;
let fp_and_pc = get_fp_and_pc();
local __fp__: felt* = fp_and_pc.fp_val;
let (evm, state, _, gas_required) = Kakarot.eth_call(
nonce,
origin,
to,
gas_limit,
gas_price,
&value,
data_len,
data,
access_list_len,
access_list,
);
let is_reverted = is_not_zero(evm.reverted);
return (evm.return_data_len, evm.return_data, 1 - is_reverted, gas_required);
}

// @notice The eth_send_transaction function as described in the spec,
// see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction
// @dev "nonce" parameter is taken from the corresponding account contract
Expand Down Expand Up @@ -260,7 +309,7 @@ func eth_send_transaction{

let (tx_info) = get_tx_info();

let (evm, state, gas_used) = Kakarot.eth_call(
let (evm, state, gas_used, _) = Kakarot.eth_call(
tx_info.nonce,
origin,
to,
Expand Down
6 changes: 3 additions & 3 deletions src/kakarot/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ namespace Kakarot {
data: felt*,
access_list_len: felt,
access_list: felt*,
) -> (model.EVM*, model.State*, felt) {
) -> (model.EVM*, model.State*, felt, felt) {
alloc_locals;
let is_regular_tx = is_not_zero(to.is_some);
let is_deploy_tx = 1 - is_regular_tx;
Expand All @@ -99,7 +99,7 @@ namespace Kakarot {

let env = Starknet.get_env(origin, gas_price);

let (evm, stack, memory, state, gas_used) = Interpreter.execute(
let (evm, stack, memory, state, gas_used, required_gas) = Interpreter.execute(
env,
address,
is_deploy_tx,
Expand All @@ -112,7 +112,7 @@ namespace Kakarot {
access_list_len,
access_list,
);
return (evm, state, gas_used);
return (evm, state, gas_used, required_gas);
}

// @notice Set the native Starknet ERC20 token used by kakarot.
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/EVM.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func execute{
let evm_address = 'target_evm_address';
let starknet_address = Account.compute_starknet_address(evm_address);
tempvar address = new model.Address(starknet_address, evm_address);
let (evm, stack, memory, state, gas_used) = Interpreter.execute(
let (evm, stack, memory, state, gas_used, _) = Interpreter.execute(
env=env,
address=address,
is_deploy_tx=0,
Expand Down

0 comments on commit 3dcbada

Please sign in to comment.