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(evm): gas usage in precompiles: limits, local gas meters #2093

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ consistent setup and dynamic gas calculations, addressing the following tickets.
- [#2088](https://github.com/NibiruChain/nibiru/pull/2088) - refactor(evm): remove outdated comment and improper error message text
- [#2089](https://github.com/NibiruChain/nibiru/pull/2089) - better handling of gas consumption within erc20 contract execution
- [#2091](https://github.com/NibiruChain/nibiru/pull/2091) - feat(evm): add fun token creation fee validation
- [#2093](https://github.com/NibiruChain/nibiru/pull/2093) - feat(evm): gas usage in precompiles: limits, local gas meters

#### Nibiru EVM | Before Audit 1 - 2024-10-18

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "TestFunTokenPrecompileLocalGas",
"sourceName": "contracts/TestFunTokenPrecompileLocalGas.sol",
"abi": [
{
"inputs": [
{
"internalType": "address",
"name": "erc20_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "string",
"name": "bech32Recipient",
"type": "string"
}
],
"name": "callBankSend",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "string",
"name": "bech32Recipient",
"type": "string"
},
{
"internalType": "uint256",
"name": "customGas",
"type": "uint256"
}
],
"name": "callBankSendLocalGas",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b50604051610951380380610951833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b61083a806101176000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806359b6ed891461003b57806390d2b5e714610057575b600080fd5b610055600480360381019061005091906104d0565b610073565b005b610071600480360381019061006c919061053f565b6101db565b005b600061080073ffffffffffffffffffffffffffffffffffffffff168260008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1686866040516024016100c49392919061066a565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161014e91906106ef565b60006040518083038160008787f1925050503d806000811461018c576040519150601f19603f3d011682016040523d82523d6000602084013e610191565b606091505b50509050806101d5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101cc90610778565b60405180910390fd5b50505050565b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16848460405160240161022b9392919061066a565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516102b591906106ef565b6000604051808303816000865af19150503d80600081146102f2576040519150601f19603f3d011682016040523d82523d6000602084013e6102f7565b606091505b505090508061033b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610332906107e4565b60405180910390fd5b505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b61036781610354565b811461037257600080fd5b50565b6000813590506103848161035e565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103dd82610394565b810181811067ffffffffffffffff821117156103fc576103fb6103a5565b5b80604052505050565b600061040f610340565b905061041b82826103d4565b919050565b600067ffffffffffffffff82111561043b5761043a6103a5565b5b61044482610394565b9050602081019050919050565b82818337600083830152505050565b600061047361046e84610420565b610405565b90508281526020810184848401111561048f5761048e61038f565b5b61049a848285610451565b509392505050565b600082601f8301126104b7576104b661038a565b5b81356104c7848260208601610460565b91505092915050565b6000806000606084860312156104e9576104e861034a565b5b60006104f786828701610375565b935050602084013567ffffffffffffffff8111156105185761051761034f565b5b610524868287016104a2565b925050604061053586828701610375565b9150509250925092565b600080604083850312156105565761055561034a565b5b600061056485828601610375565b925050602083013567ffffffffffffffff8111156105855761058461034f565b5b610591858286016104a2565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006105c68261059b565b9050919050565b6105d6816105bb565b82525050565b6105e581610354565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561062557808201518184015260208101905061060a565b60008484015250505050565b600061063c826105eb565b61064681856105f6565b9350610656818560208601610607565b61065f81610394565b840191505092915050565b600060608201905061067f60008301866105cd565b61068c60208301856105dc565b818103604083015261069e8184610631565b9050949350505050565b600081519050919050565b600081905092915050565b60006106c9826106a8565b6106d381856106b3565b93506106e3818560208601610607565b80840191505092915050565b60006106fb82846106be565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e6420776974682063757360008201527f746f6d2067617300000000000000000000000000000000000000000000000000602082015250565b60006107626027836105f6565b915061076d82610706565b604082019050919050565b6000602082019050818103600083015261079181610755565b9050919050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000600082015250565b60006107ce6017836105f6565b91506107d982610798565b602082019050919050565b600060208201905081810360008301526107fd816107c1565b905091905056fea2646970667358221220cf16927fc50953575dc9f444e1aefe75fa60bf10c7688dd039cb7503669ab76964736f6c63430008180033",
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806359b6ed891461003b57806390d2b5e714610057575b600080fd5b610055600480360381019061005091906104d0565b610073565b005b610071600480360381019061006c919061053f565b6101db565b005b600061080073ffffffffffffffffffffffffffffffffffffffff168260008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1686866040516024016100c49392919061066a565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161014e91906106ef565b60006040518083038160008787f1925050503d806000811461018c576040519150601f19603f3d011682016040523d82523d6000602084013e610191565b606091505b50509050806101d5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101cc90610778565b60405180910390fd5b50505050565b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16848460405160240161022b9392919061066a565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516102b591906106ef565b6000604051808303816000865af19150503d80600081146102f2576040519150601f19603f3d011682016040523d82523d6000602084013e6102f7565b606091505b505090508061033b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610332906107e4565b60405180910390fd5b505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b61036781610354565b811461037257600080fd5b50565b6000813590506103848161035e565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103dd82610394565b810181811067ffffffffffffffff821117156103fc576103fb6103a5565b5b80604052505050565b600061040f610340565b905061041b82826103d4565b919050565b600067ffffffffffffffff82111561043b5761043a6103a5565b5b61044482610394565b9050602081019050919050565b82818337600083830152505050565b600061047361046e84610420565b610405565b90508281526020810184848401111561048f5761048e61038f565b5b61049a848285610451565b509392505050565b600082601f8301126104b7576104b661038a565b5b81356104c7848260208601610460565b91505092915050565b6000806000606084860312156104e9576104e861034a565b5b60006104f786828701610375565b935050602084013567ffffffffffffffff8111156105185761051761034f565b5b610524868287016104a2565b925050604061053586828701610375565b9150509250925092565b600080604083850312156105565761055561034a565b5b600061056485828601610375565b925050602083013567ffffffffffffffff8111156105855761058461034f565b5b610591858286016104a2565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006105c68261059b565b9050919050565b6105d6816105bb565b82525050565b6105e581610354565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561062557808201518184015260208101905061060a565b60008484015250505050565b600061063c826105eb565b61064681856105f6565b9350610656818560208601610607565b61065f81610394565b840191505092915050565b600060608201905061067f60008301866105cd565b61068c60208301856105dc565b818103604083015261069e8184610631565b9050949350505050565b600081519050919050565b600081905092915050565b60006106c9826106a8565b6106d381856106b3565b93506106e3818560208601610607565b80840191505092915050565b60006106fb82846106be565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e6420776974682063757360008201527f746f6d2067617300000000000000000000000000000000000000000000000000602082015250565b60006107626027836105f6565b915061076d82610706565b604082019050919050565b6000602082019050818103600083015261079181610755565b9050919050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000600082015250565b60006107ce6017836105f6565b91506107d982610798565b602082019050919050565b600060208201905081810360008301526107fd816107c1565b905091905056fea2646970667358221220cf16927fc50953575dc9f444e1aefe75fa60bf10c7688dd039cb7503669ab76964736f6c63430008180033",
"linkReferences": {},
"deployedLinkReferences": {}
}
47 changes: 47 additions & 0 deletions x/evm/embeds/contracts/TestFunTokenPrecompileLocalGas.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./FunToken.sol";

contract TestFunTokenPrecompileLocalGas {
address erc20;

constructor(address erc20_) {
erc20 = erc20_;
}
onikonychev marked this conversation as resolved.
Show resolved Hide resolved

// Calls bankSend of the FunToken Precompile with the default gas.
// Internal call could use all the gas for the parent call.
function callBankSend(
uint256 amount,
string memory bech32Recipient
) public {
(bool success,) = FUNTOKEN_PRECOMPILE_ADDRESS.call(
abi.encodeWithSignature(
"bankSend(address,uint256,string)",
erc20,
amount,
bech32Recipient
)
);
require(success, "Failed to call bankSend");
}
onikonychev marked this conversation as resolved.
Show resolved Hide resolved

// Calls bankSend of the FunToken Precompile with the gas amount set in parameter.
// Internal call should fail if the gas provided is insufficient.
function callBankSendLocalGas(
uint256 amount,
string memory bech32Recipient,
uint256 customGas
) public {
(bool success,) = FUNTOKEN_PRECOMPILE_ADDRESS.call{gas: customGas}(
abi.encodeWithSignature(
"bankSend(address,uint256,string)",
erc20,
amount,
bech32Recipient
)
);
require(success, "Failed to call bankSend with custom gas");
}
onikonychev marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 9 additions & 0 deletions x/evm/embeds/embeds.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ var (
testErc20MaliciousNameJson []byte
//go:embed artifacts/contracts/TestERC20MaliciousTransfer.sol/TestERC20MaliciousTransfer.json
testErc20MaliciousTransferJson []byte
//go:embed artifacts/contracts/TestFunTokenPrecompileLocalGas.sol/TestFunTokenPrecompileLocalGas.json
testFunTokenPrecompileLocalGasJson []byte
)

var (
Expand Down Expand Up @@ -76,6 +78,12 @@ var (
Name: "TestERC20MaliciousTransfer.sol",
EmbedJSON: testErc20MaliciousTransferJson,
}
// SmartContract_TestFunTokenPrecompileLocalGas is a test contract
// which allows precompile execution with custom local gas set (calling precompile within contract)
SmartContract_TestFunTokenPrecompileLocalGas = CompiledEvmContract{
Name: "TestFunTokenPrecompileLocalGas.sol",
EmbedJSON: testFunTokenPrecompileLocalGasJson,
}
)

func init() {
Expand All @@ -86,6 +94,7 @@ func init() {
SmartContract_TestERC20.MustLoad()
SmartContract_TestERC20MaliciousName.MustLoad()
SmartContract_TestERC20MaliciousTransfer.MustLoad()
SmartContract_TestFunTokenPrecompileLocalGas.MustLoad()
}

type CompiledEvmContract struct {
Expand Down
1 change: 1 addition & 0 deletions x/evm/embeds/embeds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ func TestLoadContracts(t *testing.T) {
embeds.SmartContract_TestERC20.MustLoad()
embeds.SmartContract_TestERC20MaliciousName.MustLoad()
embeds.SmartContract_TestERC20MaliciousTransfer.MustLoad()
embeds.SmartContract_TestFunTokenPrecompileLocalGas.MustLoad()
})
}
5 changes: 5 additions & 0 deletions x/evm/evmtest/test_deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@
func (deps TestDeps) GoCtx() context.Context {
return sdk.WrapSDKContext(deps.Ctx)
}

func (deps TestDeps) ResetGasMeter() {
deps.EvmKeeper.ResetTransientGasUsed(deps.Ctx)
deps.EvmKeeper.ResetGasMeterAndConsumeGas(deps.Ctx, 0)

Check warning on line 66 in x/evm/evmtest/test_deps.go

View check run for this annotation

Codecov / codecov/patch

x/evm/evmtest/test_deps.go#L64-L66

Added lines #L64 - L66 were not covered by tests
}
155 changes: 155 additions & 0 deletions x/evm/keeper/call_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package keeper

import (
"fmt"
"math/big"
"strings"

"cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
gethabi "github.com/ethereum/go-ethereum/accounts/abi"
gethcommon "github.com/ethereum/go-ethereum/common"
gethcore "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"

"github.com/NibiruChain/nibiru/v2/x/evm"
)

// CallContract invokes a smart contract on the method specified by [methodName]
// using the given [args].
//
// Parameters:
// - ctx: The SDK context for the transaction.
// - abi: The ABI (Application Binary Interface) of the smart contract.
// - fromAcc: The Ethereum address of the account initiating the contract call.
// - contract: Pointer to the Ethereum address of the contract to be called.
// - commit: Boolean flag indicating whether to commit the transaction (true) or simulate it (false).
// - methodName: The name of the contract method to be called.
// - args: Variadic parameter for the arguments to be passed to the contract method.
//
// Note: This function handles both contract method calls and simulations,
// depending on the 'commit' parameter.
func (k Keeper) CallContract(
ctx sdk.Context,
abi *gethabi.ABI,
fromAcc gethcommon.Address,
contract *gethcommon.Address,
commit bool,
gasLimit uint64,
methodName string,
args ...any,
) (evmResp *evm.MsgEthereumTxResponse, err error) {
contractInput, err := abi.Pack(methodName, args...)
if err != nil {
return nil, fmt.Errorf("failed to pack ABI args: %w", err)
}

Check warning on line 45 in x/evm/keeper/call_contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/call_contract.go#L44-L45

Added lines #L44 - L45 were not covered by tests
evmResp, _, err = k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput, gasLimit)
return evmResp, err
}

// CallContractWithInput invokes a smart contract with the given [contractInput]
// or deploys a new contract.
//
// Parameters:
// - ctx: The SDK context for the transaction.
// - fromAcc: The Ethereum address of the account initiating the contract call.
// - contract: Pointer to the Ethereum address of the contract. Nil if new
// contract is deployed.
// - commit: Boolean flag indicating whether to commit the transaction (true)
// or simulate it (false).
// - contractInput: Hexadecimal-encoded bytes use as input data to the call.
//
// Note: This function handles both contract method calls and simulations,
// depending on the 'commit' parameter. It uses a default gas limit.
func (k Keeper) CallContractWithInput(
ctx sdk.Context,
fromAcc gethcommon.Address,
contract *gethcommon.Address,
commit bool,
contractInput []byte,
gasLimit uint64,
) (evmResp *evm.MsgEthereumTxResponse, evmObj *vm.EVM, err error) {
// This is a `defer` pattern to add behavior that runs in the case that the
// error is non-nil, creating a concise way to add extra information.
defer func() {
if err != nil {
err = fmt.Errorf("CallContractError: %w", err)
}
}()
Unique-Divine marked this conversation as resolved.
Show resolved Hide resolved
nonce := k.GetAccNonce(ctx, fromAcc)

unusedBigInt := big.NewInt(0)
evmMsg := gethcore.NewMessage(
fromAcc,
contract,
nonce,
unusedBigInt, // amount
gasLimit,
unusedBigInt, // gasFeeCap
unusedBigInt, // gasTipCap
unusedBigInt, // gasPrice
contractInput,
gethcore.AccessList{},
!commit, // isFake
)

// Apply EVM message
evmCfg, err := k.GetEVMConfig(
ctx,
sdk.ConsAddress(ctx.BlockHeader().ProposerAddress),
k.EthChainID(ctx),
)
if err != nil {
err = errors.Wrapf(err, "failed to load EVM config")
return
}

Check warning on line 105 in x/evm/keeper/call_contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/call_contract.go#L103-L105

Added lines #L103 - L105 were not covered by tests

// Generating TxConfig with an empty tx hash as there is no actual eth tx
// sent by a user
txConfig := k.TxConfig(ctx, gethcommon.BigToHash(big.NewInt(0)))

// Using tmp context to not modify the state in case of evm revert
tmpCtx, commitCtx := ctx.CacheContext()

evmResp, evmObj, err = k.ApplyEvmMsg(
tmpCtx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig, true,
)
if err != nil {
// We don't know the actual gas used, so consuming the gas limit
k.ResetGasMeterAndConsumeGas(ctx, gasLimit)
err = errors.Wrap(err, "failed to apply ethereum core message")
return
}

Check warning on line 122 in x/evm/keeper/call_contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/call_contract.go#L118-L122

Added lines #L118 - L122 were not covered by tests
if evmResp.Failed() {
k.ResetGasMeterAndConsumeGas(ctx, evmResp.GasUsed)
if !strings.Contains(evmResp.VmError, vm.ErrOutOfGas.Error()) {
if evmResp.VmError == vm.ErrExecutionReverted.Error() {
err = fmt.Errorf("VMError: %w", evm.NewExecErrorWithReason(evmResp.Ret))
return
}
err = fmt.Errorf("VMError: %s", evmResp.VmError)
return
}
err = fmt.Errorf("gas required exceeds allowance (%d)", gasLimit)
return
} else {
// Success, committing the state to ctx
if commit {
commitCtx()
totalGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed)
if err != nil {
k.ResetGasMeterAndConsumeGas(ctx, ctx.GasMeter().Limit())
return nil, nil, errors.Wrap(err, "error adding transient gas used to block")
}

Check warning on line 143 in x/evm/keeper/call_contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/call_contract.go#L141-L143

Added lines #L141 - L143 were not covered by tests
k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex))
err = k.EmitEthereumTxEvents(ctx, contract, gethcore.LegacyTxType, evmMsg, evmResp)
if err != nil {
return nil, nil, errors.Wrap(err, "error emitting ethereum tx events")
}

Check warning on line 149 in x/evm/keeper/call_contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/call_contract.go#L148-L149

Added lines #L148 - L149 were not covered by tests
blockTxIdx := uint64(txConfig.TxIndex) + 1
k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx)
}
return evmResp, evmObj, nil
}
}
Loading
Loading