Skip to content

Commit

Permalink
refactor(evm-ante)
Browse files Browse the repository at this point in the history
  • Loading branch information
Unique-Divine committed May 22, 2024
1 parent 75ae7a8 commit 4c25f5b
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 55 deletions.
33 changes: 17 additions & 16 deletions app/evmante_eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func NewAnteDecVerifyEthAcc(k AppKeepers) AnteDecVerifyEthAcc {
// - any of the msgs is not a MsgEthereumTx
// - from address is empty
// - account balance is lower than the transaction cost
func (avd AnteDecVerifyEthAcc) AnteHandle(
func (anteDec AnteDecVerifyEthAcc) AnteHandle(
ctx sdk.Context,
tx sdk.Tx,
simulate bool,
Expand Down Expand Up @@ -72,11 +72,11 @@ func (avd AnteDecVerifyEthAcc) AnteHandle(

// check whether the sender address is EOA
fromAddr := gethcommon.BytesToAddress(from)
acct := avd.EvmKeeper.GetAccount(ctx, fromAddr)
acct := anteDec.EvmKeeper.GetAccount(ctx, fromAddr)

if acct == nil {
acc := avd.AccountKeeper.NewAccountWithAddress(ctx, from)
avd.AccountKeeper.SetAccount(ctx, acc)
acc := anteDec.AccountKeeper.NewAccountWithAddress(ctx, from)
anteDec.AccountKeeper.SetAccount(ctx, acc)
acct = statedb.NewEmptyAccount()
} else if acct.IsContract() {
return ctx, errors.Wrapf(errortypes.ErrInvalidType,
Expand Down Expand Up @@ -129,13 +129,14 @@ func NewAnteDecEthGasConsume(
// - transaction or block gas meter runs out of gas
// - sets the gas meter limit
// - gas limit is greater than the block gas meter limit
func (egcd AnteDecEthGasConsume) AnteHandle(
func (anteDec AnteDecEthGasConsume) AnteHandle(
ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler,
) (sdk.Context, error) {
gasWanted := uint64(0)
// gas consumption limit already checked during CheckTx so there's no need to
// verify it again during ReCheckTx
if ctx.IsReCheckTx() {
// Then, the limit for gas consumed was already checked during CheckTx so
// there's no need to verify it again during ReCheckTx
//
// Use new context with gasWanted = 0
// Otherwise, there's an error on txmempool.postCheck (tendermint)
// that is not bubbled up. Thus, the Tx never runs on DeliverMode
Expand All @@ -144,10 +145,10 @@ func (egcd AnteDecEthGasConsume) AnteHandle(
return next(newCtx, tx, simulate)
}

evmParams := egcd.EvmKeeper.GetParams(ctx)
evmParams := anteDec.EvmKeeper.GetParams(ctx)
evmDenom := evmParams.GetEvmDenom()
chainCfg := evmParams.GetChainConfig()
ethCfg := chainCfg.EthereumConfig(egcd.EvmKeeper.EthChainID(ctx))
ethCfg := chainCfg.EthereumConfig(anteDec.EvmKeeper.EthChainID(ctx))

blockHeight := big.NewInt(ctx.BlockHeight())
homestead := ethCfg.IsHomestead(blockHeight)
Expand All @@ -156,7 +157,7 @@ func (egcd AnteDecEthGasConsume) AnteHandle(

// Use the lowest priority of all the messages as the final one.
minPriority := int64(math.MaxInt64)
baseFee := egcd.EvmKeeper.GetBaseFee(ctx, ethCfg)
baseFee := anteDec.EvmKeeper.GetBaseFee(ctx, ethCfg)

for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evm.MsgEthereumTx)
Expand All @@ -174,10 +175,10 @@ func (egcd AnteDecEthGasConsume) AnteHandle(
return ctx, errors.Wrap(err, "failed to unpack tx data")
}

if ctx.IsCheckTx() && egcd.maxGasWanted != 0 {
if ctx.IsCheckTx() && anteDec.maxGasWanted != 0 {
// We can't trust the tx gas limit, because we'll refund the unused gas.
if txData.GetGas() > egcd.maxGasWanted {
gasWanted += egcd.maxGasWanted
if txData.GetGas() > anteDec.maxGasWanted {
gasWanted += anteDec.maxGasWanted
} else {
gasWanted += txData.GetGas()
}
Expand All @@ -190,7 +191,7 @@ func (egcd AnteDecEthGasConsume) AnteHandle(
return ctx, errors.Wrapf(err, "failed to verify the fees")
}

if err = egcd.deductFee(ctx, fees, from); err != nil {
if err = anteDec.deductFee(ctx, fees, from); err != nil {
return ctx, err
}

Expand Down Expand Up @@ -242,14 +243,14 @@ func (egcd AnteDecEthGasConsume) AnteHandle(

// deductFee checks if the fee payer has enough funds to pay for the fees and deducts them.
// If the spendable balance is not enough, it tries to claim enough staking rewards to cover the fees.
func (egcd AnteDecEthGasConsume) deductFee(ctx sdk.Context, fees sdk.Coins, feePayer sdk.AccAddress) error {
func (anteDec AnteDecEthGasConsume) deductFee(ctx sdk.Context, fees sdk.Coins, feePayer sdk.AccAddress) error {
if fees.IsZero() {
return nil
}

// If the account balance is not sufficient, try to withdraw enough staking rewards

if err := egcd.EvmKeeper.DeductTxCostsFromUserBalance(ctx, fees, gethcommon.BytesToAddress(feePayer)); err != nil {
if err := anteDec.EvmKeeper.DeductTxCostsFromUserBalance(ctx, fees, gethcommon.BytesToAddress(feePayer)); err != nil {
return errors.Wrapf(err, "failed to deduct transaction costs from user balance")
}
return nil
Expand Down
126 changes: 108 additions & 18 deletions app/evmante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
gethparams "github.com/ethereum/go-ethereum/params"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/eth"
"github.com/NibiruChain/nibiru/x/evm"
"github.com/NibiruChain/nibiru/x/evm/evmtest"

Expand All @@ -21,19 +22,6 @@ var NextNoOpAnteHandler sdk.AnteHandler = func(
}

func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() {
happyCreateContractTx := func(deps *evmtest.TestDeps) *evm.MsgEthereumTx {
ethContractCreationTxParams := &evm.EvmTxArgs{
ChainID: deps.Chain.EvmKeeper.EthChainID(deps.Ctx),
Nonce: 1,
Amount: big.NewInt(10),
GasLimit: 1000,
GasPrice: big.NewInt(1),
}
tx := evm.NewTx(ethContractCreationTxParams)
tx.From = deps.Sender.EthAddr.Hex()
return tx
}

testCases := []struct {
name string
beforeTxSetup func(deps *evmtest.TestDeps, sdb *statedb.StateDB)
Expand All @@ -43,11 +31,7 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() {
{
name: "happy: sender with funds",
beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) {
gasLimit := new(big.Int).SetUint64(
gethparams.TxGasContractCreation + 500,
)
// Force account to be a smart contract
sdb.AddBalance(deps.Sender.EthAddr, gasLimit)
sdb.AddBalance(deps.Sender.EthAddr, happyGasLimit())
},
txSetup: happyCreateContractTx,
wantErr: "",
Expand Down Expand Up @@ -75,6 +59,16 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() {
},
wantErr: "failed to unpack tx data",
},
{
name: "sad: empty from addr",
beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) {},
txSetup: func(deps *evmtest.TestDeps) *evm.MsgEthereumTx {
tx := happyCreateContractTx(deps)
tx.From = ""
return tx
},
wantErr: "from address cannot be empty",
},
}

for _, tc := range testCases {
Expand All @@ -99,3 +93,99 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() {
})
}
}

func happyGasLimit() *big.Int {
return new(big.Int).SetUint64(
gethparams.TxGasContractCreation + 888,
// 888 is a cushion to account for KV store reads and writes
)
}

func gasLimitCreateContract() *big.Int {
return new(big.Int).SetUint64(
gethparams.TxGasContractCreation + 700,
)
}

func happyCreateContractTx(deps *evmtest.TestDeps) *evm.MsgEthereumTx {
ethContractCreationTxParams := &evm.EvmTxArgs{
ChainID: deps.Chain.EvmKeeper.EthChainID(deps.Ctx),
Nonce: 1,
Amount: big.NewInt(10),
GasLimit: gasLimitCreateContract().Uint64(),
GasPrice: big.NewInt(1),
}
tx := evm.NewTx(ethContractCreationTxParams)
tx.From = deps.Sender.EthAddr.Hex()
return tx
}

func (s *TestSuite) TestAnteDecEthGasConsume() {
testCases := []struct {
name string
beforeTxSetup func(deps *evmtest.TestDeps, sdb *statedb.StateDB)
txSetup func(deps *evmtest.TestDeps) *evm.MsgEthereumTx
wantErr string
maxGasWanted uint64
gasMeter sdk.GasMeter
}{
{
name: "happy: sender with funds",
beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) {
gasLimit := happyGasLimit()
balance := new(big.Int).Add(gasLimit, big.NewInt(100))
sdb.AddBalance(deps.Sender.EthAddr, balance)
},
txSetup: happyCreateContractTx,
wantErr: "",
gasMeter: eth.NewInfiniteGasMeterWithLimit(happyGasLimit().Uint64()),
maxGasWanted: 0,
},
{
name: "happy: is recheck tx",
beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) {
deps.Ctx = deps.Ctx.WithIsReCheckTx(true)
},
txSetup: happyCreateContractTx,
gasMeter: eth.NewInfiniteGasMeterWithLimit(0),
wantErr: "",
},
{
name: "sad: out of gas",
beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) {
gasLimit := happyGasLimit()
balance := new(big.Int).Add(gasLimit, big.NewInt(100))
sdb.AddBalance(deps.Sender.EthAddr, balance)
},
txSetup: happyCreateContractTx,
wantErr: "exceeds block gas limit (0)",
gasMeter: eth.NewInfiniteGasMeterWithLimit(0),
maxGasWanted: 0,
},
}

for _, tc := range testCases {
s.Run(tc.name, func() {
deps := evmtest.NewTestDeps()
stateDB := deps.StateDB()
anteDec := app.NewAnteDecEthGasConsume(
deps.Chain.AppKeepers, tc.maxGasWanted,
)

tc.beforeTxSetup(&deps, stateDB)
tx := tc.txSetup(&deps)
s.Require().NoError(stateDB.Commit())

deps.Ctx = deps.Ctx.WithIsCheckTx(true)
deps.Ctx = deps.Ctx.WithBlockGasMeter(tc.gasMeter)
_, err := anteDec.AnteHandle(
deps.Ctx, tx, false, NextNoOpAnteHandler,
)
if tc.wantErr != "" {
s.Require().ErrorContains(err, tc.wantErr)
return
}
s.Require().NoError(err)
})
}
}
59 changes: 38 additions & 21 deletions eth/gas_limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package eth

import (
"encoding/json"
fmt "fmt"
math "math"

Expand All @@ -13,7 +14,8 @@ import (
func BlockGasLimit(ctx sdk.Context) (gasLimit uint64) {
blockGasMeter := ctx.BlockGasMeter()

// Get the limit from the gas meter only if its not null and not an InfiniteGasMeter
// Get the limit from the gas meter only if its not null and not an
// InfiniteGasMeter
if blockGasMeter != nil && blockGasMeter.Limit() != 0 {
return blockGasMeter.Limit()
}
Expand Down Expand Up @@ -41,32 +43,43 @@ func BlockGasLimit(ctx sdk.Context) (gasLimit uint64) {

// NewInfiniteGasMeterWithLimit returns a reference to a new infiniteGasMeter.
func NewInfiniteGasMeterWithLimit(limit sdk.Gas) sdk.GasMeter {
return &infiniteGasMeterWithLimit{
return &InfiniteGasMeter{
consumed: 0,
limit: limit,
}
}

type infiniteGasMeterWithLimit struct {
var _ sdk.GasMeter = &InfiniteGasMeter{}

// InfiniteGasMeter: A special impl of `sdk.GasMeter` that ignores any gas
// limits, allowing an unlimited amount of gas to be consumed. This is especially
// useful for scenarios where gas consumption needs to be monitored but not
// restricted, such as during testing or in parts of the chain where constraints
// are meant to be set differently.
type InfiniteGasMeter struct {
// consumed: Tracks the amount of gas units consumed.
consumed sdk.Gas
limit sdk.Gas
// limit: Nominal unit for the gas limit, which is not enforced in a way that
// restricts consumption.
limit sdk.Gas
}

// GasConsumedToLimit returns the gas limit if gas consumed is past the limit,
// otherwise it returns the consumed gas.
// NOTE: This behavior is only called when recovering from panic when
// BlockGasMeter consumes gas past the limit.
func (g *infiniteGasMeterWithLimit) GasConsumedToLimit() sdk.Gas {
//
// Note that This function is used when recovering
// from a panic in "BlockGasMeter" when the consumed gas passes the limit.
func (g *InfiniteGasMeter) GasConsumedToLimit() sdk.Gas {
return g.consumed
}

// GasConsumed returns the gas consumed from the GasMeter.
func (g *infiniteGasMeterWithLimit) GasConsumed() sdk.Gas {
func (g *InfiniteGasMeter) GasConsumed() sdk.Gas {
return g.consumed
}

// Limit returns the gas limit of the GasMeter.
func (g *infiniteGasMeterWithLimit) Limit() sdk.Gas {
func (g *InfiniteGasMeter) Limit() sdk.Gas {
return g.limit
}

Expand All @@ -81,7 +94,7 @@ func addUint64Overflow(a, b uint64) (uint64, bool) {
}

// ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit or out of gas.
func (g *infiniteGasMeterWithLimit) ConsumeGas(amount sdk.Gas, descriptor string) {
func (g *InfiniteGasMeter) ConsumeGas(amount sdk.Gas, descriptor string) {
var overflow bool
// TODO: Should we set the consumed field after overflow checking?
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
Expand All @@ -96,37 +109,41 @@ func (g *infiniteGasMeterWithLimit) ConsumeGas(amount sdk.Gas, descriptor string
// Use case: This functionality enables refunding gas to the trasaction or block gas pools so that
// EVM-compatible chains can fully support the go-ethereum StateDb interface.
// See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *infiniteGasMeterWithLimit) RefundGas(amount sdk.Gas, descriptor string) {
func (g *InfiniteGasMeter) RefundGas(amount sdk.Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{Descriptor: descriptor})
}

g.consumed -= amount
}

// IsPastLimit returns true if gas consumed is past limit, otherwise it returns false.
func (g *infiniteGasMeterWithLimit) IsPastLimit() bool {
// IsPastLimit returns true if gas consumed is past limit, otherwise it returns
// false. In the case of the the [InfiniteGasMeter], this always returns false.
func (g *InfiniteGasMeter) IsPastLimit() bool {
return false
}

// IsOutOfGas returns true if gas consumed is greater than or equal to gas limit, otherwise it returns false.
func (g *infiniteGasMeterWithLimit) IsOutOfGas() bool {
// IsOutOfGas returns true if gas consumed is greater than or equal to gas limit,
// otherwise it returns false. In the case of the the [InfiniteGasMeter], this
// always returns false for unrestricted gas consumption.
func (g *InfiniteGasMeter) IsOutOfGas() bool {
return false
}

// String returns the BasicGasMeter's gas limit and gas consumed.
func (g *infiniteGasMeterWithLimit) String() string {
return fmt.Sprintf("InfiniteGasMeter:\n consumed: %d", g.consumed)
func (g *InfiniteGasMeter) String() string {
data := map[string]uint64{"consumed": g.consumed, "limit": g.limit}
jsonData, _ := json.Marshal(data)
return fmt.Sprintf("InfiniteGasMeter: %s", jsonData)
}

// GasRemaining returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeterWithLimit) GasRemaining() sdk.Gas {
func (g *InfiniteGasMeter) GasRemaining() sdk.Gas {
return math.MaxUint64
}

// ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a
// negative gas consumed amount.
// Copied from cosmos-sdk
// ErrorNegativeGasConsumed defines an error thrown when the amount of gas
// refunded results in a negative gas consumed amount.
type ErrorNegativeGasConsumed struct {
Descriptor string
}
Expand Down

0 comments on commit 4c25f5b

Please sign in to comment.