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: add indexer tests #115

Merged
merged 16 commits into from
Dec 3, 2024
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ ignore:
- "x/ibc/testing"
- "x/evm/contracts"
- "**/*.sol"
- "tests/"
7 changes: 7 additions & 0 deletions app/ante/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func (suite *AnteTestSuite) createTestApp(tempDir string) (*minievmapp.MinitiaAp
err = app.EVMKeeper.Params.Set(ctx, params)
suite.NoError(err)

err = app.EVMKeeper.Initialize(ctx)
suite.NoError(err)

return app, ctx
}

Expand Down Expand Up @@ -131,3 +134,7 @@ func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []
func TestAnteTestSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}

func noopAnteHandler(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}
27 changes: 15 additions & 12 deletions app/ante/fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

// feeDeductionGasAmount is a estimated gas amount of fee payment
const feeDeductionGasAmount = 250_000

// GasFreeFeeDecorator is a decorator that sets the gas meter to infinite before calling the inner DeductFeeDecorator
// and then resets the gas meter to the original value after the inner DeductFeeDecorator is called.
//
Expand All @@ -34,6 +31,9 @@ func NewGasFreeFeeDecorator(
}
}

// gasLimitForFeeDeduction is the gas limit used for fee deduction.
const gasLimitForFeeDeduction = 1_000_000

func (fd GasFreeFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
Expand All @@ -43,19 +43,22 @@ func (fd GasFreeFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo
fees := feeTx.GetFee()
feeDenom, err := fd.ek.GetFeeDenom(ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()))
if !(err == nil && len(fees) == 1 && fees[0].Denom == feeDenom) {
if simulate && fees.IsZero() {
// Charge gas for fee deduction simulation
//
// At gas simulation normally gas amount is zero, so the gas is not charged in the simulation.
ctx.GasMeter().ConsumeGas(feeDeductionGasAmount, "fee deduction")
}

return fd.inner.AnteHandle(ctx, tx, simulate, next)
}

// If the fee contains only one denom and it is the fee denom, set the gas meter to infinite
// to avoid gas consumption for fee deduction.
gasMeter := ctx.GasMeter()
ctx, err = fd.inner.AnteHandle(ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()), tx, simulate, next)
return ctx.WithGasMeter(gasMeter), err
ctx, err = fd.inner.AnteHandle(ctx.WithGasMeter(storetypes.NewGasMeter(gasLimitForFeeDeduction)), tx, simulate, noopAnteHandler)
// restore the original gas meter
ctx = ctx.WithGasMeter(gasMeter)
if err != nil {
return ctx, err
}

return next(ctx, tx, simulate)
}

func noopAnteHandler(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}
19 changes: 11 additions & 8 deletions app/ante/fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ func (suite *AnteTestSuite) Test_NotSpendingGasForTxWithFeeDenom() {
gasLimit := uint64(200_000)
atomFeeAmount := sdk.NewCoins(sdk.NewCoin("atom", math.NewInt(200)))

suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, feeAmount.MulInt(math.NewInt(10)))
suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, atomFeeAmount.MulInt(math.NewInt(10)))
err := suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, feeAmount.MulInt(math.NewInt(10)))
suite.Require().NoError(err)
err = suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, atomFeeAmount.MulInt(math.NewInt(10)))
suite.Require().NoError(err)

// Case 1. only fee denom
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
Expand All @@ -40,34 +42,35 @@ func (suite *AnteTestSuite) Test_NotSpendingGasForTxWithFeeDenom() {
suite.Require().NoError(err)

gasMeter := storetypes.NewGasMeter(500000)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil)
_, err = feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler)
suite.Require().NoError(err)
suite.Require().Zero(gasMeter.GasConsumed(), "should not consume gas for fee deduction")

// Case 2. fee denom and other denom
suite.txBuilder.SetFeeAmount(feeAmount.Add(atomFeeAmount...))

gasMeter = storetypes.NewGasMeter(500000)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler)
suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction")

// Case 3. other denom
suite.txBuilder.SetFeeAmount(feeAmount.Add(atomFeeAmount...))

gasMeter = storetypes.NewGasMeter(500000)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler)
suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction")

// Case 4. no fee
suite.txBuilder.SetFeeAmount(sdk.NewCoins())

gasMeter = storetypes.NewGasMeter(500000)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler)
suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction")

// Case 5. simulate gas consumption
suite.txBuilder.SetFeeAmount(sdk.NewCoins())

gasMeter = storetypes.NewGasMeter(500000)
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, true, nil)
suite.Require().Greater(gasMeter.GasConsumed(), uint64(250000), "should consume gas for fee deduction")
feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, true, noopAnteHandler)
suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction")
}
19 changes: 12 additions & 7 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,19 @@ func NewMinitiaApp(
}

// setup indexer
if evmIndexer, kvIndexerKeeper, kvIndexerModule, streamingManager, err := setupIndexer(app, appOpts, indexerDB, kvindexerDB); err != nil {
evmIndexer, kvIndexerKeeper, kvIndexerModule, streamingManager, err := setupIndexer(app, appOpts, indexerDB, kvindexerDB)
if err != nil {
tmos.Exit(err.Error())
} else if kvIndexerKeeper != nil && kvIndexerModule != nil && streamingManager != nil {
} else if kvIndexerKeeper != nil && kvIndexerModule != nil {
// register kvindexer keeper and module, and register services.
app.SetKVIndexer(kvIndexerKeeper, kvIndexerModule)
}

// register evm indexer
app.SetEVMIndexer(evmIndexer)
// register evm indexer
app.SetEVMIndexer(evmIndexer)

// override base-app's streaming manager
app.SetStreamingManager(*streamingManager)
}
// override base-app's streaming manager
app.SetStreamingManager(*streamingManager)

// register upgrade handler for later use
app.RegisterUpgradeHandlers(app.configurator)
Expand Down Expand Up @@ -602,6 +603,10 @@ func (app *MinitiaApp) Close() error {
return err
}

if app.evmIndexer != nil {
app.evmIndexer.Stop()
}

return nil
}

Expand Down
3 changes: 0 additions & 3 deletions app/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,4 @@ const (
moveMsgPublishModuleBundle = "/initia.move.v1.MsgPublish"
moveMsgExecuteEntryFunction = "/initia.move.v1.MsgExecute"
moveMsgExecuteScript = "/initia.move.v1.MsgScript"

// UpgradeName gov proposal name
UpgradeName = "0.0.0"
)
2 changes: 1 addition & 1 deletion app/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func setupIndexer(
indexerDB, kvindexerDB dbm.DB,
) (evmindexer.EVMIndexer, *kvindexerkeeper.Keeper, *kvindexermodule.AppModuleBasic, *storetypes.StreamingManager, error) {
// setup evm indexer
evmIndexer, err := evmindexer.NewEVMIndexer(indexerDB, app.appCodec, app.Logger(), app.txConfig, app.EVMKeeper, app.OPChildKeeper)
evmIndexer, err := evmindexer.NewEVMIndexer(indexerDB, app.appCodec, app.Logger(), app.txConfig, app.EVMKeeper)
if err != nil {
return nil, nil, nil, nil, err
}
Expand Down
12 changes: 11 additions & 1 deletion app/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
tmtypes "github.com/cometbft/cometbft/types"
dbm "github.com/cosmos/cosmos-db"

"cosmossdk.io/math"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
Expand All @@ -22,7 +23,9 @@ import (

opchildtypes "github.com/initia-labs/OPinit/x/opchild/types"

"github.com/initia-labs/minievm/types"
evmconfig "github.com/initia-labs/minievm/x/evm/config"
evmtypes "github.com/initia-labs/minievm/x/evm/types"
)

// defaultConsensusParams defines the default Tendermint consensus params used in
Expand Down Expand Up @@ -65,7 +68,7 @@ func setup(db *dbm.DB, withGenesis bool) (*MinitiaApp, GenesisState) {
)

if withGenesis {
return app, NewDefaultGenesisState(encCdc.Codec, app.BasicModuleManager, sdk.DefaultBondDenom)
return app, NewDefaultGenesisState(encCdc.Codec, app.BasicModuleManager, types.BaseDenom)
beer-1 marked this conversation as resolved.
Show resolved Hide resolved
}

return app, GenesisState{}
Expand Down Expand Up @@ -128,11 +131,18 @@ func SetupWithGenesisAccounts(
app.AppCodec().MustUnmarshalJSON(genesisState[opchildtypes.ModuleName], &opchildGenesis)
opchildGenesis.Params.Admin = sdk.AccAddress(valSet.Validators[0].Address.Bytes()).String()
opchildGenesis.Params.BridgeExecutors = []string{sdk.AccAddress(valSet.Validators[0].Address.Bytes()).String()}
opchildGenesis.Params.MinGasPrices = sdk.NewDecCoins(sdk.NewDecCoin(types.BaseDenom, math.NewInt(1_000_000_000)))

// set validators and delegations
opchildGenesis = *opchildtypes.NewGenesisState(opchildGenesis.Params, validators, nil)
genesisState[opchildtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&opchildGenesis)

// set evm genesis params
var evmGenesis evmtypes.GenesisState
app.AppCodec().MustUnmarshalJSON(genesisState[evmtypes.ModuleName], &evmGenesis)
evmGenesis.Params.GasRefundRatio = math.LegacyZeroDec()
genesisState[evmtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&evmGenesis)

// update total supply
bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(), []banktypes.Metadata{}, []banktypes.SendEnabled{})
genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis)
Expand Down
138 changes: 138 additions & 0 deletions indexer/abci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package indexer_test

import (
"math/big"
"sync"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/initia-labs/minievm/tests"
evmtypes "github.com/initia-labs/minievm/x/evm/types"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)

func Test_ListenFinalizeBlock(t *testing.T) {
app, addrs, privKeys := tests.CreateApp(t)
indexer := app.EVMIndexer()
defer app.Close()

tx, evmTxHash := tests.GenerateCreateERC20Tx(t, app, privKeys[0])
_, finalizeRes := tests.ExecuteTxs(t, app, tx)
tests.CheckTxResult(t, finalizeRes.TxResults[0], true)

events := finalizeRes.TxResults[0].Events
createEvent := events[len(events)-3]
require.Equal(t, evmtypes.EventTypeContractCreated, createEvent.GetType())

contractAddr, err := hexutil.Decode(createEvent.Attributes[0].Value)
require.NoError(t, err)

// listen finalize block
ctx, err := app.CreateQueryContext(0, false)
require.NoError(t, err)

// check the tx is indexed
evmTx, err := indexer.TxByHash(ctx, evmTxHash)
require.NoError(t, err)
require.NotNil(t, evmTx)

// mint 1_000_000 tokens to the first address
tx, evmTxHash = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000))
finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx)
tests.CheckTxResult(t, finalizeRes.TxResults[0], true)

// listen finalize block
ctx, err = app.CreateQueryContext(0, false)
require.NoError(t, err)

// check the tx is indexed
evmTx, err = indexer.TxByHash(ctx, evmTxHash)
require.NoError(t, err)
require.NotNil(t, evmTx)

// check the block header is indexed
header, err := indexer.BlockHeaderByNumber(ctx, uint64(finalizeReq.Height))
require.NoError(t, err)
require.NotNil(t, header)
require.Equal(t, finalizeReq.Height, header.Number.Int64())

}

func Test_ListenFinalizeBlock_Subscribe(t *testing.T) {
app, _, privKeys := tests.CreateApp(t)
indexer := app.EVMIndexer()
defer app.Close()

blockChan, logsChan, pendChan := indexer.Subscribe()
close(pendChan)

tx, evmTxHash := tests.GenerateCreateERC20Tx(t, app, privKeys[0])

reqHeight := app.LastBlockHeight() + 1
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
for {
select {
case block := <-blockChan:
require.NotNil(t, block)
require.Equal(t, reqHeight, block.Number.Int64())
wg.Done()
case logs := <-logsChan:
require.NotNil(t, logs)

for _, log := range logs {
require.Equal(t, evmTxHash, log.TxHash)
require.Equal(t, uint64(reqHeight), log.BlockNumber)
}

wg.Done()
case <-time.After(10 * time.Second):
t.Error("timeout waiting for pending transaction")
wg.Done()
}
}
}()

finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx)
require.Equal(t, reqHeight, finalizeReq.Height)
tests.CheckTxResult(t, finalizeRes.TxResults[0], true)

events := finalizeRes.TxResults[0].Events
createEvent := events[len(events)-3]
require.Equal(t, evmtypes.EventTypeContractCreated, createEvent.GetType())

wg.Wait()
}

func Test_ListenFinalizeBlock_ContractCreation(t *testing.T) {
app, _, privKeys := tests.CreateApp(t)
indexer := app.EVMIndexer()
defer app.Close()

tx, evmTxHash := tests.GenerateCreateInitiaERC20Tx(t, app, privKeys[0])
_, finalizeRes := tests.ExecuteTxs(t, app, tx)
tests.CheckTxResult(t, finalizeRes.TxResults[0], true)

events := finalizeRes.TxResults[0].Events
createEvent := events[len(events)-3]
require.Equal(t, evmtypes.EventTypeContractCreated, createEvent.GetType())

contractAddr, err := hexutil.Decode(createEvent.Attributes[0].Value)
require.NoError(t, err)

// check the tx is indexed
ctx, err := app.CreateQueryContext(0, false)
require.NoError(t, err)

receipt, err := indexer.TxReceiptByHash(ctx, evmTxHash)
require.NoError(t, err)
require.NotNil(t, receipt)

// contract creation should have contract address in receipt
require.Equal(t, contractAddr, receipt.ContractAddress.Bytes())
}
Loading
Loading