diff --git a/CHANGELOG.md b/CHANGELOG.md index aff303d42f..2b2b5e77ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ handling for NIBI via WNIBI. - [#2379](https://github.com/NibiruChain/nibiru/pull/2379) - fix(evm): disallow permissionless creation of FunToken mappings when tokens do not already have metadata. - [#2381](https://github.com/NibiruChain/nibiru/pull/2381) - feat(evm): Overwrite ERC20 metadata for stNIBI on Nibiru Testnet 2, and make the contract upgradeable. +- [#2384](https://github.com/NibiruChain/nibiru/pull/2384) - feat: Multi VM Gas Token Flexibility ### Dependencies - Bump `base-x` from 3.0.10 to 3.0.11 ([#2355](https://github.com/NibiruChain/nibiru/pull/2355)) diff --git a/app/ante.go b/app/ante.go index 79c2d5adfe..0e509f672a 100644 --- a/app/ante.go +++ b/app/ante.go @@ -79,7 +79,7 @@ func NewAnteHandlerNonEVM( authante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper), // TODO: spike(security): Does minimum gas price of 0 pose a risk? // ticket: https://github.com/NibiruChain/nibiru/issues/1916 - authante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper, opts.TxFeeChecker), + ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.EvmKeeper, opts.BankKeeper, opts.FeegrantKeeper, opts.TxFeeChecker), // ----------- Ante Handlers: devgas devgasante.NewDevGasPayoutDecorator(opts.DevGasBankKeeper, opts.DevGasKeeper), // ----------- Ante Handlers: Keys and signatures diff --git a/app/ante/deductfee.go b/app/ante/deductfee.go new file mode 100644 index 0000000000..9c6986e34c --- /dev/null +++ b/app/ante/deductfee.go @@ -0,0 +1,290 @@ +package ante + +import ( + "fmt" + "math" + + sdkioerrors "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/types" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/app/appconst" + "github.com/NibiruChain/nibiru/v2/eth" + "github.com/NibiruChain/nibiru/v2/x/evm" + evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper" +) + +var ( + _ sdk.AnteDecorator = DeductFeeDecorator{} +) + +// DeductFeeDecorator deducts fees from the fee payer. The fee payer is the fee granter (if specified) or first signer of the tx. +// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error. +// Call next AnteHandler if fees successfully deducted. +// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator +type DeductFeeDecorator struct { + accountKeeper authkeeper.AccountKeeper + evmkeeper *evmkeeper.Keeper + bankKeeper types.BankKeeper + feegrantKeeper FeegrantKeeper + txFeeChecker authante.TxFeeChecker +} + +func NewDeductFeeDecorator(ak authkeeper.AccountKeeper, ek *evmkeeper.Keeper, bk types.BankKeeper, fk FeegrantKeeper, tfc authante.TxFeeChecker) DeductFeeDecorator { + if tfc == nil { + tfc = checkTxFeeWithValidatorMinGasPrices + } + + return DeductFeeDecorator{ + accountKeeper: ak, + evmkeeper: ek, + bankKeeper: bk, + feegrantKeeper: fk, + txFeeChecker: tfc, + } +} + +func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkioerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 { + return ctx, sdkioerrors.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas") + } + + if ctx.BlockHeight() == 0 { + return next(ctx, tx, simulate) + } + + pausedGasMeter := ctx.GasMeter() + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + + var ( + priority int64 + err error + ) + + fee := feeTx.GetFee() + if !simulate { + fee, priority, err = dfd.txFeeChecker(ctx, tx) + if err != nil { + return ctx, err + } + } + if err := dfd.checkDeductFee(ctx, tx, fee); err != nil { + return ctx, err + } + + // TODO: print gas consumption values to verify this works as expected + + newCtx := ctx.WithPriority(priority).WithGasMeter(pausedGasMeter) + fmt.Printf("newCtx.GasMeter().GasConsumed(): %v\n", newCtx.GasMeter().GasConsumed()) + + return next(newCtx, tx, simulate) +} + +func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error { + feeTx, ok := sdkTx.(sdk.FeeTx) + if !ok { + return sdkioerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil { + return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName) + } + + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() + deductFeesFrom := feePayer + + // if feegranter set deduct fee from feegranter account. + // this works with only when feegrant enabled. + if feeGranter != nil { + if dfd.feegrantKeeper == nil { + return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") + } else if !feeGranter.Equals(feePayer) { + err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, sdkTx.GetMsgs()) + if err != nil { + return sdkioerrors.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) + } + } + + deductFeesFrom = feeGranter + } + + deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) + if deductFeesFromAcc == nil { + return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) + } + + // deduct the fees + if !fee.IsZero() { + err := DeductFees(dfd.accountKeeper, dfd.evmkeeper, dfd.bankKeeper, ctx, deductFeesFromAcc, fee) + if err != nil { + return err + } + } + + events := sdk.Events{ + sdk.NewEvent( + sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), + sdk.NewAttribute(sdk.AttributeKeyFeePayer, deductFeesFrom.String()), + ), + } + ctx.EventManager().EmitEvents(events) + + return nil +} + +// DeductFees deducts fees from the given account. +func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error { + if !fees.IsValid() { + return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees) + } + + gasMeterBefore := ctx.GasMeter() + gasConsumedBefore := gasMeterBefore.GasConsumed() + baseOpGasConsumed := uint64(0) + + defer func() { + // NOTE: we have to refund the entire gasMeterBefore because it's modified by AfterOp + // stateDB.getStateObject() reads from state using the local root ctx which affects the gas meter + gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "") + gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant") + }() + + if fees[0].Denom == appconst.BondDenom { + err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees) + if err == nil { + return nil + } + + baseOpGasConsumed = ctx.GasMeter().GasConsumed() + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + + // fallback to WNIBI + err = DeductFeesWithWNIBI(ctx, accountKeeper, evmKeeper, acc, fees) + if err == nil { + return nil + } + + return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient balance across supported gas tokens to cover %s", fees[0].Amount) + } else { + return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee denom must be %s, got %s", appconst.BondDenom, fees[0].Denom) + } +} + +// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails. +func DeductFeesWithWNIBI( + ctx sdk.Context, + accountKeeper authante.AccountKeeper, + evmKeeper *evmkeeper.Keeper, + acc types.AccountI, + fees sdk.Coins, +) error { + wnibi := evmKeeper.GetParams(ctx).CanonicalWnibi + + stateDB := evmKeeper.Bank.StateDB + if stateDB == nil { + stateDB = evmKeeper.NewStateDB(ctx, evmKeeper.TxConfig(ctx, gethcommon.Hash{})) + } + defer func() { + evmKeeper.Bank.StateDB = nil + }() + + evmObj := evmKeeper.NewEVM(ctx, evm.MOCK_GETH_MESSAGE, evmKeeper.GetEVMConfig(ctx), nil, stateDB) + wnibiBal, err := evmKeeper.ERC20().BalanceOf(wnibi.Address, eth.NibiruAddrToEthAddr(acc.GetAddress()), ctx, evmObj) + if err != nil { + return sdkioerrors.Wrapf(err, "failed to get WNIBI balance for account %s", acc.GetAddress()) + } + + feeCollector := eth.NibiruAddrToEthAddr(accountKeeper.GetModuleAddress(types.FeeCollectorName)) + feesAmount := fees[0].Amount + + sender := evm.Addrs{ + Bech32: acc.GetAddress(), + Eth: eth.NibiruAddrToEthAddr(acc.GetAddress()), + } + if wnibiBal.Cmp(evm.NativeToWei(feesAmount.BigInt())) >= 0 { + nonce := evmKeeper.GetAccNonce(ctx, sender.Eth) + _, err = evmKeeper.ConvertEvmToCoinForWNIBI( + ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(types.FeeCollectorName), + sdkmath.NewIntFromBigInt(evm.NativeToWei(feesAmount.BigInt())), + nil, + ) + if err != nil { + return sdkioerrors.Wrapf(err, "failed to transfer WNIBI from %s to %s", sender.Eth.Hex(), feeCollector.Hex()) + } + if err := acc.SetSequence(nonce); err != nil { + return sdkioerrors.Wrapf(err, "failed to set sequence to %d", nonce) + } + accountKeeper.SetAccount(ctx, acc) + return nil + } + return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient balance across supported gas tokens to cover %s", feesAmount) +} + +// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per +// unit of gas is fixed and set by each validator, can the tx priority is computed from the gas price. +func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, 0, sdkioerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + feeCoins := feeTx.GetFee() + gas := feeTx.GetGas() + + // Ensure that the provided fees meet a minimum threshold for the validator, + // if this is a CheckTx. This is only for local mempool purposes, and thus + // is only ran on check tx. + if ctx.IsCheckTx() { + minGasPrices := ctx.MinGasPrices() + if !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdkmath.LegacyNewDec(int64(gas)) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + if !feeCoins.IsAnyGTE(requiredFees) { + return nil, 0, sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + } + } + + priority := getTxPriority(feeCoins, int64(gas)) + return feeCoins, priority, nil +} + +// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price +// provided in a transaction. +// NOTE: This implementation should be used with a great consideration as it opens potential attack vectors +// where txs with multiple coins could not be prioritize as expected. +func getTxPriority(fee sdk.Coins, gas int64) int64 { + var priority int64 + for _, c := range fee { + p := int64(math.MaxInt64) + gasPrice := c.Amount.QuoRaw(gas) + if gasPrice.IsInt64() { + p = gasPrice.Int64() + } + if priority == 0 || p < priority { + priority = p + } + } + + return priority +} diff --git a/app/ante/expected_keeper.go b/app/ante/expected_keeper.go new file mode 100644 index 0000000000..1d9d1c39df --- /dev/null +++ b/app/ante/expected_keeper.go @@ -0,0 +1,10 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// FeegrantKeeper defines the expected feegrant keeper. +type FeegrantKeeper interface { + UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error +} diff --git a/app/ante/handler_opts.go b/app/ante/handler_opts.go index 2a6783fc9a..53e6e3cede 100644 --- a/app/ante/handler_opts.go +++ b/app/ante/handler_opts.go @@ -4,7 +4,6 @@ import ( sdkioerrors "cosmossdk.io/errors" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkante "github.com/cosmos/cosmos-sdk/x/auth/ante" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" @@ -53,5 +52,3 @@ func (opts *AnteHandlerOptions) ValidateAndClean() error { func AnteHandlerError(shortDesc string) error { return sdkioerrors.Wrapf(sdkerrors.ErrLogic, "%s is required for AnteHandler", shortDesc) } - -type TxFeeChecker func(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, int64, error) diff --git a/app/evmante/evmante_gas_consume.go b/app/evmante/evmante_gas_consume.go index bc2d71bffb..f793a29c7d 100644 --- a/app/evmante/evmante_gas_consume.go +++ b/app/evmante/evmante_gas_consume.go @@ -9,6 +9,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/NibiruChain/nibiru/v2/app/ante" "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/keeper" @@ -17,18 +18,21 @@ import ( // AnteDecEthGasConsume validates enough intrinsic gas for the transaction and // gas consumption. type AnteDecEthGasConsume struct { - evmKeeper *EVMKeeper - maxGasWanted uint64 + evmKeeper *EVMKeeper + accountKeeper evm.AccountKeeper + maxGasWanted uint64 } // NewAnteDecEthGasConsume creates a new EthGasConsumeDecorator func NewAnteDecEthGasConsume( k *EVMKeeper, + ak evm.AccountKeeper, maxGasWanted uint64, ) AnteDecEthGasConsume { return AnteDecEthGasConsume{ - evmKeeper: k, - maxGasWanted: maxGasWanted, + evmKeeper: k, + accountKeeper: ak, + maxGasWanted: maxGasWanted, } } @@ -123,7 +127,6 @@ func (anteDec AnteDecEthGasConsume) AnteHandle( minPriority = priority } } - ctx.EventManager().EmitEvents(events) blockGasLimit := eth.BlockGasLimit(ctx) @@ -165,10 +168,24 @@ func (anteDec AnteDecEthGasConsume) deductFee( return nil } - if err := anteDec.evmKeeper.DeductTxCostsFromUserBalance( + err := anteDec.evmKeeper.DeductTxCostsFromUserBalance( ctx, fees, gethcommon.BytesToAddress(feePayer), - ); err != nil { - return sdkioerrors.Wrapf(err, "failed to deduct transaction costs from user balance") + ) + if err == nil { + return nil + } + if !sdkioerrors.IsOf(err, sdkerrors.ErrInsufficientFunds) { + return err + } + + acc := anteDec.accountKeeper.GetAccount(ctx, feePayer) + if acc == nil { + return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", feePayer) + } + err = ante.DeductFeesWithWNIBI(ctx, anteDec.accountKeeper, anteDec.evmKeeper, acc, fees) + if err == nil { + return nil } - return nil + + return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient balance across supported gas tokens to cover %s", fees[0].Amount) } diff --git a/app/evmante/evmante_gas_consume_test.go b/app/evmante/evmante_gas_consume_test.go index 322d6ea36f..6f891effd0 100644 --- a/app/evmante/evmante_gas_consume_test.go +++ b/app/evmante/evmante_gas_consume_test.go @@ -10,20 +10,23 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" "github.com/NibiruChain/nibiru/v2/x/evm/statedb" + + gethcommon "github.com/ethereum/go-ethereum/common" + gethcrypto "github.com/ethereum/go-ethereum/crypto" ) func (s *TestSuite) TestAnteDecEthGasConsume() { testCases := []struct { name string - beforeTxSetup func(deps *evmtest.TestDeps, sdb *statedb.StateDB) + beforeTxSetup func(deps *evmtest.TestDeps, sdb *statedb.StateDB, wnibi gethcommon.Address) 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) { + name: "happy: sender with funds (native)", + beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB, _ gethcommon.Address) { gasLimit := happyGasLimit() balance := evm.NativeToWei(new(big.Int).Add(gasLimit, big.NewInt(100))) sdb.AddBalanceSigned(deps.Sender.EthAddr, balance) @@ -33,9 +36,26 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { gasMeter: eth.NewInfiniteGasMeterWithLimit(happyGasLimit().Uint64()), maxGasWanted: 0, }, + { + name: "happy: sender with funds (wnibi)", + beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB, wnibi gethcommon.Address) { + acc := deps.App.AccountKeeper.NewAccountWithAddress(deps.Ctx, eth.EthAddrToNibiruAddr(deps.Sender.EthAddr)) + deps.App.AccountKeeper.SetAccount(deps.Ctx, acc) + // Fund the contract wnibi with unibi and set the mapping slot of the sender to be the equivalent + // of happyGasLimit in unibi, so that the sender has enough wnibi to pay for gas + sdb.AddBalanceSigned(wnibi, evm.NativeToWei(happyGasLimit())) + slot := CalcMappingSlot(deps.Sender.EthAddr, 3) + value := gethcommon.BigToHash(evm.NativeToWei(happyGasLimit())) + sdb.SetState(wnibi, slot, value) + }, + txSetup: evmtest.HappyCreateContractTx, + wantErr: "", + gasMeter: eth.NewInfiniteGasMeterWithLimit(happyGasLimit().Uint64()), + maxGasWanted: 0, + }, { name: "happy: is recheck tx", - beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { + beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB, _ gethcommon.Address) { deps.Ctx = deps.Ctx.WithIsReCheckTx(true) }, txSetup: evmtest.HappyCreateContractTx, @@ -44,7 +64,7 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { }, { name: "sad: out of gas", - beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { + beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB, _ gethcommon.Address) { gasLimit := happyGasLimit() balance := evm.NativeToWei(new(big.Int).Add(gasLimit, big.NewInt(100))) sdb.AddBalanceSigned(deps.Sender.EthAddr, balance) @@ -59,12 +79,14 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() + deps.DeployWNIBI(&s.Suite) stateDB := deps.NewStateDB() anteDec := evmante.NewAnteDecEthGasConsume( - deps.App.EvmKeeper, tc.maxGasWanted, + deps.App.EvmKeeper, deps.App.AccountKeeper, tc.maxGasWanted, ) - tc.beforeTxSetup(&deps, stateDB) + wnibi := deps.EvmKeeper.GetParams(deps.Ctx).CanonicalWnibi.Address + tc.beforeTxSetup(&deps, stateDB, wnibi) tx := tc.txSetup(&deps) s.Require().NoError(stateDB.Commit()) @@ -81,3 +103,9 @@ func (s *TestSuite) TestAnteDecEthGasConsume() { }) } } + +func CalcMappingSlot(key gethcommon.Address, baseSlot uint64) gethcommon.Hash { + addrPadded := gethcommon.LeftPadBytes(key.Bytes(), 32) + slotPadded := gethcommon.LeftPadBytes(new(big.Int).SetUint64(baseSlot).Bytes(), 32) + return gethcrypto.Keccak256Hash(addrPadded, slotPadded) +} diff --git a/app/evmante/evmante_handler.go b/app/evmante/evmante_handler.go index a9c2f7d0f4..5f051812d8 100644 --- a/app/evmante/evmante_handler.go +++ b/app/evmante/evmante_handler.go @@ -19,7 +19,7 @@ func NewAnteHandlerEVM( NewEthSigVerificationDecorator(options.EvmKeeper), NewAnteDecVerifyEthAcc(options.EvmKeeper, options.AccountKeeper), CanTransferDecorator{options.EvmKeeper}, - NewAnteDecEthGasConsume(options.EvmKeeper, options.MaxTxGasWanted), + NewAnteDecEthGasConsume(options.EvmKeeper, options.AccountKeeper, options.MaxTxGasWanted), NewAnteDecEthIncrementSenderSequence(options.EvmKeeper, options.AccountKeeper), ante.AnteDecoratorGasWanted{}, // emit eth tx hash and index at the very last ante handler. diff --git a/app/evmante/evmante_verify_eth_acc.go b/app/evmante/evmante_verify_eth_acc.go index ca71baed1f..47f2a53458 100644 --- a/app/evmante/evmante_verify_eth_acc.go +++ b/app/evmante/evmante_verify_eth_acc.go @@ -41,6 +41,9 @@ func (anteDec AnteDecVerifyEthAcc) AnteHandle( next sdk.AnteHandler, ) (newCtx sdk.Context, err error) { for i, msg := range tx.GetMsgs() { + // TODO: Branch ctx here with infinite gas meter + // Constraint -> Zero gas cost to passs through this function + msgEthTx, ok := msg.(*evm.MsgEthereumTx) if !ok { return ctx, sdkioerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil)) @@ -72,9 +75,32 @@ func (anteDec AnteDecVerifyEthAcc) AnteHandle( if err := keeper.CheckSenderBalance( evm.NativeToWei(acct.BalanceNative.ToBig()), txData, - ); err != nil { - return ctx, sdkioerrors.Wrap(err, "failed to check sender balance") + ); err == nil { + continue + } + + wnibi := anteDec.evmKeeper.GetParams(ctx).CanonicalWnibi + + stateDB := anteDec.evmKeeper.Bank.StateDB + if stateDB == nil { + stateDB = anteDec.evmKeeper.NewStateDB(ctx, anteDec.evmKeeper.TxConfig(ctx, gethcommon.Hash{})) } + defer func() { + anteDec.evmKeeper.Bank.StateDB = nil + }() + + evmObj := anteDec.evmKeeper.NewEVM(ctx, evm.MOCK_GETH_MESSAGE, anteDec.evmKeeper.GetEVMConfig(ctx), nil /*tracer*/, stateDB /*statedb*/) + wnibiBal, err := anteDec.evmKeeper.ERC20().BalanceOf(wnibi.Address, fromAddr, ctx, evmObj) + + cost := txData.Cost() + if wnibi.Address != (gethcommon.Address{}) && err == nil && wnibiBal.Cmp(cost) >= 0 { + continue + } + + return ctx, sdkioerrors.Wrapf( + sdkerrors.ErrInsufficientFunds, + "sender balance < tx cost (%s < %s)", acct.BalanceNative.ToBig(), cost, + ) } return next(ctx, tx, simulate) } diff --git a/app/evmante/evmante_verify_eth_acc_test.go b/app/evmante/evmante_verify_eth_acc_test.go index 9f7fddb489..cdad6caf1c 100644 --- a/app/evmante/evmante_verify_eth_acc_test.go +++ b/app/evmante/evmante_verify_eth_acc_test.go @@ -3,9 +3,13 @@ package evmante_test import ( "math/big" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/common/hexutil" gethparams "github.com/ethereum/go-ethereum/params" "github.com/NibiruChain/nibiru/v2/app/evmante" + "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" "github.com/NibiruChain/nibiru/v2/x/evm/statedb" @@ -19,13 +23,38 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() { wantErr string }{ { - name: "happy: sender with funds", + name: "happy: sender with funds (native)", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { sdb.AddBalanceSigned(deps.Sender.EthAddr, evm.NativeToWei(happyGasLimit())) }, txSetup: evmtest.HappyCreateContractTx, wantErr: "", }, + { + name: "happy: sender with funds (wnibi)", + beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) { + err := testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + sdk.NewCoins(sdk.NewCoin(evm.EVMBankDenom, sdk.NewIntFromBigInt(happyGasLimit()))), + ) + s.Require().NoError(err) + wnibi := deps.App.EvmKeeper.ERC20().GetParams(deps.Ctx).CanonicalWnibi.Address + sdb.AddBalanceSigned(deps.Sender.EthAddr, evm.NativeToWei(happyGasLimit())) + + s.Require().NoError(err) + evmtest.ExecuteNibiTransferTo(deps, s.T(), wnibi, (*hexutil.Big)(evm.NativeToWei(happyGasLimit()))) + balance, err := deps.EvmKeeper.Balance(deps.Ctx, &evm.QueryBalanceRequest{ + Address: deps.Sender.EthAddr.Hex(), + }) + s.Require().NoError(err) + // Make sure there is zero native balance + s.Require().Equal(balance.Balance, "0") + }, + txSetup: evmtest.HappyCreateContractTx, + wantErr: "", + }, { name: "sad: sender has insufficient gas balance", beforeTxSetup: func(deps *evmtest.TestDeps, sdb *statedb.StateDB) {}, @@ -64,6 +93,7 @@ func (s *TestSuite) TestAnteDecoratorVerifyEthAcc_CheckTx() { for _, tc := range testCases { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() + deps.DeployWNIBI(&s.Suite) stateDB := deps.NewStateDB() anteDec := evmante.NewAnteDecVerifyEthAcc(deps.App.EvmKeeper, &deps.App.AccountKeeper) diff --git a/contrib/scripts/localnet.sh b/contrib/scripts/localnet.sh index 86c58e9a9d..43ba3fc8f6 100755 --- a/contrib/scripts/localnet.sh +++ b/contrib/scripts/localnet.sh @@ -222,11 +222,26 @@ add_genesis_param '.app_state.oracle.exchange_rates[0].pair = "ubtc:uusd"' add_genesis_param '.app_state.oracle.exchange_rates[0].exchange_rate = "'"$price_btc"'"' add_genesis_param '.app_state.oracle.exchange_rates[1].pair = "ueth:uusd"' add_genesis_param '.app_state.oracle.exchange_rates[1].exchange_rate = "'"$price_eth"'"' +add_genesis_param '.app_state.bank.supply[0].amount = "30000420000000"' + +# Simple helpers for auth accounts in genesis +get_auth_accounts_len() { + cat $CHAIN_DIR/config/genesis.json | jq '.app_state.auth.accounts | length' +} # ------------------------------------------------------------------------ -# Gentx +# Prepare WNIBI account and merge into genesis # ------------------------------------------------------------------------ +echo_info "Setting WNIBI auth account_number to next open index" +NEXT_ACC_NUM=$(get_auth_accounts_len) +jq ".auth.accounts[0].base_account.account_number=\"${NEXT_ACC_NUM}\"" contrib/wnibi.json > contrib/wnibi.tmp.json +mv contrib/wnibi.tmp.json contrib/wnibi.json + +jq --slurpfile evm contrib/wnibi.json '.app_state.evm = $evm[0].evm | .app_state.auth.accounts += $evm[0].auth.accounts |.app_state.bank.balances += $evm[0].bank.balances' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json +mv $CHAIN_DIR/config/genesis_tmp.json $CHAIN_DIR/config/genesis.json + +# Gentx echo_info "Adding gentx validator..." if $BINARY genesis gentx $val_key_name 900000000unibi --chain-id $CHAIN_ID; then echo_success "Successfully added gentx" diff --git a/contrib/wnibi.json b/contrib/wnibi.json new file mode 100644 index 0000000000..cadc29b501 --- /dev/null +++ b/contrib/wnibi.json @@ -0,0 +1,66 @@ +{ + "auth": { + "accounts": [ + { + "@type": "/eth.types.v1.EthAccount", + "base_account": { + "account_number": "3", + "address": "nibi1pjk0v60cg347e2pxjyarc6uk4n2tq25hhymgg9", + "pub_key": null, + "sequence": "0" + }, + "code_hash": "0xffb88e0eb48147949565e65de3ec8a54b746214da7b9dd5b9a8a3ae7df46193b" + } + ] + }, + "evm": { + "accounts": [ + { + "address": "0x0CaCF669f8446BeCA826913a3c6B96aCD4b02a97", + "code": "6080604052600436106100a05760003560e01c8063313ce56711610064578063313ce567146101b257806370a08231146101dd57806395d89b411461021a578063a9059cbb14610245578063d0e30db014610282578063dd62ed3e1461028c576100af565b806306fdde03146100b9578063095ea7b3146100e457806318160ddd1461012157806323b872dd1461014c5780632e1a7d4d14610189576100af565b366100af576100ad6102c9565b005b6100b76102c9565b005b3480156100c557600080fd5b506100ce61036f565b6040516100db9190610b18565b60405180910390f35b3480156100f057600080fd5b5061010b60048036038101906101069190610bd3565b6103fd565b6040516101189190610c2e565b60405180910390f35b34801561012d57600080fd5b506101366104ef565b6040516101439190610c58565b60405180910390f35b34801561015857600080fd5b50610173600480360381019061016e9190610c73565b6104f7565b6040516101809190610c2e565b60405180910390f35b34801561019557600080fd5b506101b060048036038101906101ab9190610cc6565b61085b565b005b3480156101be57600080fd5b506101c7610995565b6040516101d49190610d0f565b60405180910390f35b3480156101e957600080fd5b5061020460048036038101906101ff9190610d2a565b6109a8565b6040516102119190610c58565b60405180910390f35b34801561022657600080fd5b5061022f6109c0565b60405161023c9190610b18565b60405180910390f35b34801561025157600080fd5b5061026c60048036038101906102679190610bd3565b610a4e565b6040516102799190610c2e565b60405180910390f35b61028a6102c9565b005b34801561029857600080fd5b506102b360048036038101906102ae9190610d57565b610a63565b6040516102c09190610c58565b60405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546103189190610dc6565b925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040516103659190610c58565b60405180910390a2565b6000805461037c90610e29565b80601f01602080910402602001604051908101604052809291908181526020018280546103a890610e29565b80156103f55780601f106103ca576101008083540402835291602001916103f5565b820191906000526020600020905b8154815290600101906020018083116103d857829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104dd9190610c58565b60405180910390a36001905092915050565b600047905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561054557600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415801561061d57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b1561073f5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156106ab57600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107379190610e5a565b925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461078e9190610e5a565b9250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107e49190610dc6565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516108489190610c58565b60405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156108a757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108f69190610e5a565b925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610943573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b658260405161098a9190610c58565b60405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b600180546109cd90610e29565b80601f01602080910402602001604051908101604052809291908181526020018280546109f990610e29565b8015610a465780601f10610a1b57610100808354040283529160200191610a46565b820191906000526020600020905b815481529060010190602001808311610a2957829003601f168201915b505050505081565b6000610a5b3384846104f7565b905092915050565b6004602052816000526040600020602052806000526040600020600091509150505481565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ac2578082015181840152602081019050610aa7565b60008484015250505050565b6000601f19601f8301169050919050565b6000610aea82610a88565b610af48185610a93565b9350610b04818560208601610aa4565b610b0d81610ace565b840191505092915050565b60006020820190508181036000830152610b328184610adf565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b6a82610b3f565b9050919050565b610b7a81610b5f565b8114610b8557600080fd5b50565b600081359050610b9781610b71565b92915050565b6000819050919050565b610bb081610b9d565b8114610bbb57600080fd5b50565b600081359050610bcd81610ba7565b92915050565b60008060408385031215610bea57610be9610b3a565b5b6000610bf885828601610b88565b9250506020610c0985828601610bbe565b9150509250929050565b60008115159050919050565b610c2881610c13565b82525050565b6000602082019050610c436000830184610c1f565b92915050565b610c5281610b9d565b82525050565b6000602082019050610c6d6000830184610c49565b92915050565b600080600060608486031215610c8c57610c8b610b3a565b5b6000610c9a86828701610b88565b9350506020610cab86828701610b88565b9250506040610cbc86828701610bbe565b9150509250925092565b600060208284031215610cdc57610cdb610b3a565b5b6000610cea84828501610bbe565b91505092915050565b600060ff82169050919050565b610d0981610cf3565b82525050565b6000602082019050610d246000830184610d00565b92915050565b600060208284031215610d4057610d3f610b3a565b5b6000610d4e84828501610b88565b91505092915050565b60008060408385031215610d6e57610d6d610b3a565b5b6000610d7c85828601610b88565b9250506020610d8d85828601610b88565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610dd182610b9d565b9150610ddc83610b9d565b9250828201905080821115610df457610df3610d97565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610e4157607f821691505b602082108103610e5457610e53610dfa565b5b50919050565b6000610e6582610b9d565b9150610e7083610b9d565b9250828203905081811115610e8857610e87610d97565b5b9291505056fea2646970667358221220e2bbe4e79fbff010e16625cff639a72785d4dbf62ac4a60275168b532643766464736f6c63430008180033", + "storage": [ + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x57726170706564204e696269727500000000000000000000000000000000001c" + }, + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x574e49424900000000000000000000000000000000000000000000000000000a" + }, + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000002", + "value": "0x0000000000000000000000000000000000000000000000000000000000000012" + }, + { + "key": "0x129a734ac9181ee8080f010318d1e3feb1872428a702f862729497a66a2d0cc4", + "value": "0x00000000000000000000000000000000000000000000000b6255df5f50080000" + }, + { + "key": "0xc3b612fd7cee4db97ec418c3898f39b2fa9f468935409a0955dc5a9e94d0ae58", + "value": "0x00000000000000000000000000000000000000000000000b6255df5f50080000" + } + ] + } + ], + "funtoken_mappings": [], + "params": { + "canonical_wnibi": "0x0CaCF669f8446BeCA826913a3c6B96aCD4b02a97", + "create_funtoken_fee": "10000000000", + "evm_channels": [], + "extra_eips": [] + } + }, + "bank": { + "balances": [ + { + "address": "nibi1pjk0v60cg347e2pxjyarc6uk4n2tq25hhymgg9", + "coins": [ + { + "amount": "420000000", + "denom": "unibi" + } + ] + } + ] + } +} diff --git a/evm-e2e/test/setup.ts b/evm-e2e/test/setup.ts index ab766a6156..e83bcdb2d8 100644 --- a/evm-e2e/test/setup.ts +++ b/evm-e2e/test/setup.ts @@ -1,11 +1,19 @@ import { config } from "dotenv" -import { ethers, getDefaultProvider, Wallet } from "ethers" +import { ethers, Mnemonic } from "ethers" +import { HDNodeWallet } from "ethers/wallet" config() const provider = new ethers.JsonRpcProvider(process.env.JSON_RPC_ENDPOINT) -const account = Wallet.fromPhrase(process.env.MNEMONIC, provider) +const mnemonic = Mnemonic.fromPhrase(process.env.MNEMONIC!) +// First account derived from the mnemonic at index 0, already funded with native tokens +const account = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/0").connect(provider) +// Second account derived from the same mnemonic but at index 1, used for testing with zero native balance +const account2 = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/1").connect(provider) const TEST_TIMEOUT = Number(process.env.TEST_TIMEOUT) || 15000 const TX_WAIT_TIMEOUT = Number(process.env.TX_WAIT_TIMEOUT) || 5000 -export { account, provider, TEST_TIMEOUT, TX_WAIT_TIMEOUT } + + +export { account, account2, provider, TEST_TIMEOUT, TX_WAIT_TIMEOUT } + diff --git a/evm-e2e/test/utils.ts b/evm-e2e/test/utils.ts index 5493608d8f..c5ee757b63 100644 --- a/evm-e2e/test/utils.ts +++ b/evm-e2e/test/utils.ts @@ -1,5 +1,6 @@ import { wnibiCaller } from "@nibiruchain/evm-core" import { + ethers, ContractFactory, ContractTransactionResponse, parseEther, @@ -105,3 +106,27 @@ export const deployContractWNIBI = async (): Promise<{ export const numberToHex = (num: Number) => { return "0x" + num.toString(16) } + +export const getWNIBI = async (): Promise<{ + contract: WNIBI & DeploymentTx +}> => { + const { abi, bytecode } = WNIBI_JSON + const factory = new ContractFactory(abi, bytecode, account) + + let contract: ethers.Contract + + try { + const code = await account.provider.getCode(localnetWNIBIAddress) + if (code !== "0x") { + // Contract already deployed + console.log(`Contract already deployed at ${localnetWNIBIAddress}`) + contract = new ethers.Contract(localnetWNIBIAddress, abi, account) + return { contract: contract as unknown as WNIBI & DeploymentTx } + } + } catch (error) { + console.warn(`Failed to check code at ${localnetWNIBIAddress}:`, error) + // Fall through to deploy new contract + } +} + +const localnetWNIBIAddress = "0x0CaCF669f8446BeCA826913a3c6B96aCD4b02a97" \ No newline at end of file diff --git a/evm-e2e/test/wnibi_gas.test.ts b/evm-e2e/test/wnibi_gas.test.ts new file mode 100644 index 0000000000..27d30f66c0 --- /dev/null +++ b/evm-e2e/test/wnibi_gas.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "@jest/globals" +import { parseUnits, toBigInt, Wallet } from "ethers" + +import { account, account2, provider, TEST_TIMEOUT, TX_WAIT_TIMEOUT } from "./setup" +import { getWNIBI } from "./utils" + +describe("WNIBI used as gas tests", () => { + it( + "Interaction of account with zero native balance but some WNIBI balance", + async () => { + const { contract: wnibi } = await getWNIBI() + const walletBalWei = await provider.getBalance(account2.address) + + // Make sure there is no balance initially + expect(walletBalWei).toEqual(0n) + + // Send some WNIBI to account2 to use as gas token + { + let tx = await wnibi.transfer(account2.address, parseUnits("10", 18)) + await tx.wait() + const wnibiBal = await wnibi.balanceOf(account2.address) + expect(wnibiBal).toEqual(parseUnits("10", 18)) + } + + { + const alice = Wallet.createRandom() + // Ensure non-zero fees so WNIBI balance drops below 9 after transfer + const feeOverrides = { + maxFeePerGas: parseUnits("1", "gwei"), + maxPriorityFeePerGas: parseUnits("0", "gwei"), + } + let tx = await wnibi.connect(account2).transfer(alice.address, parseUnits("1", 18), feeOverrides) + await tx.wait() + const wnibiBal = await wnibi.balanceOf(alice.address) + expect(wnibiBal).toEqual(parseUnits("1", 18)) + const wnibiBal2 = await wnibi.balanceOf(account2.address) + // Less than 9 WNIBI left after transfer and gas + expect(wnibiBal2).toBeLessThan(parseUnits("9", 18)) + } + }, + TEST_TIMEOUT, + ) +}) \ No newline at end of file diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index 91973e6d2a..d709bdffe5 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -47,6 +47,26 @@ func ExecuteNibiTransfer(deps *TestDeps, t *testing.T) (*evm.MsgEthereumTx, *evm return ethTxMsg, resp } +func ExecuteNibiTransferTo(deps *TestDeps, t *testing.T, recipient gethcommon.Address, value *hexutil.Big) (*evm.MsgEthereumTx, *evm.MsgEthereumTxResponse) { + nonce := deps.NewStateDB().GetNonce(deps.Sender.EthAddr) + + txArgs := evm.JsonTxArgs{ + From: &deps.Sender.EthAddr, + To: &recipient, + Nonce: (*hexutil.Uint64)(&nonce), + Value: value, + } + ethTxMsg, gethSigner, krSigner, err := GenerateEthTxMsgAndSigner(txArgs, deps, deps.Sender) + require.NoError(t, err) + err = ethTxMsg.Sign(gethSigner, krSigner) + require.NoError(t, err) + + resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), ethTxMsg) + require.NoError(t, err) + require.Empty(t, resp.VmError) + return ethTxMsg, resp +} + type DeployContractResult struct { TxResp *evm.MsgEthereumTxResponse EthTxMsg *evm.MsgEthereumTx diff --git a/x/evm/mock_msg.go b/x/evm/mock_msg.go new file mode 100644 index 0000000000..46ac8ddc61 --- /dev/null +++ b/x/evm/mock_msg.go @@ -0,0 +1,21 @@ +package evm + +import ( + "github.com/ethereum/go-ethereum/core" + gethcore "github.com/ethereum/go-ethereum/core/types" +) + +var MOCK_GETH_MESSAGE = core.Message{ + To: nil, + From: EVM_MODULE_ADDRESS, + Nonce: 0, + Value: Big0, // amount + GasLimit: 0, + GasPrice: Big0, + GasFeeCap: Big0, + GasTipCap: Big0, + Data: []byte{}, + AccessList: gethcore.AccessList{}, + SkipNonceChecks: false, + SkipFromEOACheck: false, +}