diff --git a/src/kakarot/interpreter.cairo b/src/kakarot/interpreter.cairo index 0ee727f45..36af2c792 100644 --- a/src/kakarot/interpreter.cairo +++ b/src/kakarot/interpreter.cairo @@ -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*, @@ -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; @@ -860,7 +862,7 @@ 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 @@ -868,7 +870,7 @@ namespace Interpreter { 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]); @@ -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( @@ -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. @@ -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. @@ -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 diff --git a/src/kakarot/kakarot.cairo b/src/kakarot/kakarot.cairo index f19251bad..223a7e9f8 100644 --- a/src/kakarot/kakarot.cairo +++ b/src/kakarot/kakarot.cairo @@ -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, @@ -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 @@ -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, diff --git a/src/kakarot/library.cairo b/src/kakarot/library.cairo index a87249025..711de3a62 100644 --- a/src/kakarot/library.cairo +++ b/src/kakarot/library.cairo @@ -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; @@ -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, @@ -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. diff --git a/tests/fixtures/EVM.cairo b/tests/fixtures/EVM.cairo index 4b6dbfebb..e2a850989 100644 --- a/tests/fixtures/EVM.cairo +++ b/tests/fixtures/EVM.cairo @@ -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,