From 92763337cd4ec02ff143c3d8a21c7e921bae7321 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Sat, 24 Aug 2024 22:39:11 -0500 Subject: [PATCH] merge: get tests passing with non-default fee cap commit e7ffdadf55aa1aecf19f2add744067cfcd57e083 Author: Unique-Divine Date: Sat Aug 24 22:30:00 2024 -0500 fix: more RPC tests and refactor more commit e17c50ddffc6767679a67b92bd5dc6fe3eeae94f Author: Unique-Divine Date: Fri Aug 23 22:46:46 2024 -0500 test(e2e): get tests passing with non-default fee cap --- app/evmante/evmante_mempool_fees.go | 20 +- app/server/start.go | 83 ++++---- e2e/evm/bun.lockb | Bin 129508 -> 216205 bytes e2e/evm/test/basic_queries.test.ts | 77 +++++--- .../test/contract_infinite_loop_gas.test.ts | 9 +- e2e/evm/test/contract_send_nibi.test.ts | 11 +- e2e/evm/test/erc20.test.ts | 6 +- e2e/evm/test/utils.ts | 39 +++- eth/rpc/backend/call_tx.go | 5 +- eth/rpc/backend/call_tx_test.go | 10 +- eth/rpc/backend/tx_info.go | 2 + eth/rpc/rpc.go | 2 +- eth/rpc/rpcapi/eth_api_test.go | 185 +++++++++++------- eth/rpc/rpcapi/net_api_test.go | 13 ++ x/common/testutil/testnetwork/network.go | 38 ++-- x/common/testutil/testnetwork/start_node.go | 171 ++++++++++++++++ x/common/testutil/testnetwork/util.go | 126 ------------ .../testutil/testnetwork/validator_node.go | 39 +++- 18 files changed, 511 insertions(+), 325 deletions(-) create mode 100644 eth/rpc/rpcapi/net_api_test.go create mode 100644 x/common/testutil/testnetwork/start_node.go diff --git a/app/evmante/evmante_mempool_fees.go b/app/evmante/evmante_mempool_fees.go index defb5cb6e..1b37415cb 100644 --- a/app/evmante/evmante_mempool_fees.go +++ b/app/evmante/evmante_mempool_fees.go @@ -3,6 +3,7 @@ package evmante import ( "cosmossdk.io/errors" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -38,14 +39,15 @@ func (d MempoolGasPriceDecorator) AnteHandle( } minGasPrice := ctx.MinGasPrices().AmountOf(d.evmKeeper.GetParams(ctx).EvmDenom) + baseFeeMicronibi := d.evmKeeper.GetBaseFee(ctx) + baseFeeDec := math.LegacyNewDecFromBigInt(baseFeeMicronibi) // if MinGasPrices is not set, skip the check if minGasPrice.IsZero() { return next(ctx, tx, simulate) + } else if minGasPrice.LT(baseFeeDec) { + minGasPrice = baseFeeDec } - baseFeeMicronibi := d.evmKeeper.GetBaseFee(ctx) - baseFeeWei := evm.NativeToWei(baseFeeMicronibi) - for _, msg := range tx.GetMsgs() { ethTx, ok := msg.(*evm.MsgEthereumTx) if !ok { @@ -56,16 +58,18 @@ func (d MempoolGasPriceDecorator) AnteHandle( ) } - effectiveGasPriceWei := ethTx.GetEffectiveGasPrice(baseFeeWei) - effectiveGasPrice := evm.WeiToNative(effectiveGasPriceWei) - - if sdk.NewDecFromBigInt(effectiveGasPrice).LT(minGasPrice) { + baseFeeWei := evm.NativeToWei(baseFeeMicronibi) + effectiveGasPriceDec := math.LegacyNewDecFromBigInt( + evm.WeiToNative(ethTx.GetEffectiveGasPrice(baseFeeWei)), + ) + if effectiveGasPriceDec.LT(minGasPrice) { + // if sdk.NewDecFromBigInt(effectiveGasPrice).LT(minGasPrice) { return ctx, errors.Wrapf( sdkerrors.ErrInsufficientFee, "provided gas price < minimum local gas price (%s < %s). "+ "Please increase the priority tip (for EIP-1559 txs) or the gas prices "+ "(for access list or legacy txs)", - effectiveGasPrice.String(), minGasPrice.String(), + effectiveGasPriceDec, minGasPrice, ) } } diff --git a/app/server/start.go b/app/server/start.go index 680673bb1..f18c0101f 100644 --- a/app/server/start.go +++ b/app/server/start.go @@ -29,6 +29,7 @@ import ( dbm "github.com/cometbft/cometbft-db" abciserver "github.com/cometbft/cometbft/abci/server" tcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" + "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cometbft/cometbft/node" "github.com/cometbft/cometbft/p2p" @@ -44,7 +45,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" + sdkserver "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/api" serverconfig "github.com/cosmos/cosmos-sdk/server/config" servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" @@ -101,7 +102,7 @@ For profiling and benchmarking purposes, CPU profiling can be enabled via the '- which accepts a path for the resulting pprof file. `, PreRunE: func(cmd *cobra.Command, _ []string) error { - serverCtx := server.GetServerContextFromCmd(cmd) + serverCtx := sdkserver.GetServerContextFromCmd(cmd) // Bind flags to the Context's Viper so the app construction can set // options accordingly. @@ -110,11 +111,11 @@ which accepts a path for the resulting pprof file. return err } - _, err = server.GetPruningOptionsFromFlags(serverCtx.Viper) + _, err = sdkserver.GetPruningOptionsFromFlags(serverCtx.Viper) return err }, RunE: func(cmd *cobra.Command, _ []string) error { - serverCtx := server.GetServerContextFromCmd(cmd) + serverCtx := sdkserver.GetServerContextFromCmd(cmd) clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { return err @@ -143,7 +144,7 @@ which accepts a path for the resulting pprof file. // amino is needed here for backwards compatibility of REST routes err = startInProcess(serverCtx, clientCtx, opts) - errCode, ok := err.(server.ErrorCode) + errCode, ok := err.(sdkserver.ErrorCode) if !ok { return err } @@ -158,18 +159,18 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(Address, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(Transport, "socket", "Transport protocol: socket, grpc") cmd.Flags().String(TraceStore, "", "Enable KVStore tracing to an output file") - cmd.Flags().String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 5000unibi)") //nolint:lll - cmd.Flags().IntSlice(server.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") - cmd.Flags().Uint64(server.FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") - cmd.Flags().Uint64(server.FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") - cmd.Flags().Bool(server.FlagInterBlockCache, true, "Enable inter-block caching") + cmd.Flags().String(sdkserver.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 5000unibi)") //nolint:lll + cmd.Flags().IntSlice(sdkserver.FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary") + cmd.Flags().Uint64(sdkserver.FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Uint64(sdkserver.FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Bool(sdkserver.FlagInterBlockCache, true, "Enable inter-block caching") cmd.Flags().String(CPUProfile, "", "Enable CPU profiling and write to the provided file") - cmd.Flags().Bool(server.FlagTrace, false, "Provide full stack traces for errors in ABCI Log") - cmd.Flags().String(server.FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)") - cmd.Flags().Uint64(server.FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')") - cmd.Flags().Uint64(server.FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") //nolint:lll - cmd.Flags().Uint(server.FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") - cmd.Flags().Uint64(server.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") + cmd.Flags().Bool(sdkserver.FlagTrace, false, "Provide full stack traces for errors in ABCI Log") + cmd.Flags().String(sdkserver.FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)") + cmd.Flags().Uint64(sdkserver.FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')") + cmd.Flags().Uint64(sdkserver.FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") //nolint:lll + cmd.Flags().Uint(sdkserver.FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") + cmd.Flags().Uint64(sdkserver.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") cmd.Flags().String(AppDBBackend, "", "The type of database for application and snapshots databases") cmd.Flags().Bool(GRPCOnly, false, "Start the node in gRPC query only mode without Tendermint process") @@ -204,20 +205,20 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(TLSCertPath, "", "the cert.pem file path for the server TLS configuration") cmd.Flags().String(TLSKeyPath, "", "the key.pem file path for the server TLS configuration") - cmd.Flags().Uint64(server.FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") - cmd.Flags().Uint32(server.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") + cmd.Flags().Uint64(sdkserver.FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") + cmd.Flags().Uint32(sdkserver.FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") // add support for all Tendermint-specific command line options tcmd.AddNodeFlags(cmd) return cmd } -func startStandAlone(ctx *server.Context, opts StartOptions) error { +func startStandAlone(ctx *sdkserver.Context, opts StartOptions) error { addr := ctx.Viper.GetString(Address) transport := ctx.Viper.GetString(Transport) home := ctx.Viper.GetString(flags.FlagHome) - db, err := openDB(home, server.GetAppDBBackend(ctx.Viper)) + db, err := openDB(home, sdkserver.GetAppDBBackend(ctx.Viper)) if err != nil { return err } @@ -271,16 +272,16 @@ func startStandAlone(ctx *server.Context, opts StartOptions) error { }() // Wait for SIGINT or SIGTERM signal - return server.WaitForQuitSignals() + return sdkserver.WaitForQuitSignals() } // legacyAminoCdc is used for the legacy REST API -func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOptions) (err error) { +func startInProcess(ctx *sdkserver.Context, clientCtx client.Context, opts StartOptions) (err error) { cfg := ctx.Config home := cfg.RootDir logger := ctx.Logger - db, err := openDB(home, server.GetAppDBBackend(ctx.Viper)) + db, err := openDB(home, sdkserver.GetAppDBBackend(ctx.Viper)) if err != nil { logger.Error("failed to open DB", "error", err.Error()) return err @@ -381,16 +382,14 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt ethmetricsexp.Setup(conf.JSONRPC.MetricsAddress) } - var idxer eth.EVMTxIndexer + var evmIdxer eth.EVMTxIndexer if conf.JSONRPC.EnableIndexer { - idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(ctx.Viper)) + idxer, err := OpenEVMIndexer(ctx, ctx.Logger, clientCtx, home) if err != nil { logger.Error("failed to open evm indexer DB", "error", err.Error()) return err } - - idxLogger := ctx.Logger.With("indexer", "evm") - idxer = indexer.NewKVIndexer(idxDB, idxLogger, clientCtx) + evmIdxer = idxer } if conf.API.Enable || conf.JSONRPC.Enable { @@ -508,7 +507,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt tmEndpoint := "/websocket" tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &conf, idxer) + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &conf, evmIdxer) if err != nil { return err } @@ -531,7 +530,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt // we do not need to start Rosetta or handle any Tendermint related processes. if gRPCOnly { // wait for signal capture and gracefully return - return server.WaitForQuitSignals() + return sdkserver.WaitForQuitSignals() } var rosettaSrv crgserver.Server @@ -584,7 +583,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt } } // Wait for SIGINT or SIGTERM signal - return server.WaitForQuitSignals() + return sdkserver.WaitForQuitSignals() } // OpenIndexerDB opens the custom eth indexer db, using the same db backend as the main app @@ -593,6 +592,22 @@ func OpenIndexerDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) return dbm.NewDB("evmindexer", backendType, dataDir) } +func OpenEVMIndexer( + ctx *sdkserver.Context, + logger log.Logger, + clientCtx client.Context, + homeDir string, +) (eth.EVMTxIndexer, error) { + idxDB, err := OpenIndexerDB(homeDir, sdkserver.GetAppDBBackend(ctx.Viper)) + if err != nil { + logger.Error("failed to open evm indexer DB", "error", err.Error()) + return nil, err + } + + idxLogger := ctx.Logger.With("indexer", "evm") + return indexer.NewKVIndexer(idxDB, idxLogger, clientCtx), nil +} + func openTraceWriter(traceWriterFile string) (w io.Writer, err error) { if traceWriterFile == "" { return @@ -614,15 +629,15 @@ func startTelemetry(cfg config.Config) (*telemetry.Metrics, error) { } // WaitForQuitSignals waits for SIGINT and SIGTERM and returns. -func WaitForQuitSignals() server.ErrorCode { +func WaitForQuitSignals() sdkserver.ErrorCode { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) sig := <-sigs - return server.ErrorCode{Code: int(sig.(syscall.Signal)) + 128} + return sdkserver.ErrorCode{Code: int(sig.(syscall.Signal)) + 128} } // wrapCPUProfile runs callback in a goroutine, then wait for quit signals. -func wrapCPUProfile(ctx *server.Context, callback func() error) error { +func wrapCPUProfile(ctx *sdkserver.Context, callback func() error) error { if cpuProfile := ctx.Viper.GetString(CPUProfile); cpuProfile != "" { f, err := os.Create(cpuProfile) if err != nil { diff --git a/e2e/evm/bun.lockb b/e2e/evm/bun.lockb index c93ef4901bab83e5c0109dff84f5404d3f259300..b88cb597fc795eb38c2b453f92a1c170f2bf6e68 100755 GIT binary patch literal 216205 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
lsE1GlZxUx9+ zAIS^mdGhc*1mZ)S8%TwDc%zURA|NuYkRT^NR>_IW$lZ`uQSNk(>y+6(u}2BEoIW>c z#RMPL7w8jr%~Y}la!MHMmg_fN?4+U+Ru*01%ublZlAF==WhZY|R+;rJf9=TTm8U2a zn&dQYlsE=ovSO}6(8CCo>GR`w%U0|ygI}?e%qiGnk2&!!DI?V5)QdJeDY$`Z$n(1@ ztC##9CrEIY0n$P9Qz210U4nf_5AJ`$^~VV8$|FSM=$LR8w}{)zeSqv^PD79?hHzA< zBuHDh92&yDJGC8am(o~fhm^1cAMeA?!Z_u=JlE#5C$FRlSt()YIb@l*c?8mOf{<=Q z&5dy}CWZ)rZ#>Yzm%d0K6vAB-bgxZ;%v|t@T@MFiBeajtfapIxS;!S}U(kyFku`uN z%QLU5-Na$$7X-|YY~|USC65G$wpcVK!BM8rAhuC%i!X};f`a9lXR9<8AIVw{IySm5 zEF1f5I)dS2IW#i0FzBbgoF!Yg7K^pE=k z$kE}^>d=nV!l)tjCAWju78EF;9J0jrFkbL|-2SQ9F$@I};X?XKqztc(3oi8atspEf z_jUdxN^;QzTLv~anT3nWD5?V|O8&wk)1lEuA?h@NOUqI+18xf_qbdUz^)jxTFAQ8% zBk(FvpYZa5tk;tUkEya3)uz5@DI%lN*@r^ycD*c!+ChxYaqbAKG6{OtcPv^r{#4T) z2&zD=({83H6Hux&M^$jM+7V4jbqOVcPPOX!=X@opQtd-Ukk&g zzC-U@%7H!0GwToA<;3e~JP&CRv<<)(CLPL2DCSC%K)kDsd=TehV7k77#EF*SIf+3*>M+Spb*+Cmj>fm!*_R<4~Ui z7ML&-A^x$pWLuepB~5|*5drIId=>8G&JRFtd*%^3=-(VB}Nb#y5>J(u zq@4<=C~3)^>>w-4@?}Ly3Z|?SE$=Ey(9}ORJ!WWf1v|>xSZH%a`ASGXr8m;!Nf-mV WeGmlrdO{rOK%|_lLB{{5|Nb9awa1qL delta 31055 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 diff --git a/e2e/evm/test/basic_queries.test.ts b/e2e/evm/test/basic_queries.test.ts index a974a70a1..c5ac208bd 100644 --- a/e2e/evm/test/basic_queries.test.ts +++ b/e2e/evm/test/basic_queries.test.ts @@ -1,20 +1,27 @@ -import { describe, expect, it } from "@jest/globals" +import { describe, expect, it, jest } from "@jest/globals" import { toBigInt, - Wallet, parseEther, keccak256, AbiCoder, - ethers, + hexlify, + TransactionRequest, } from "ethers" import { account, provider } from "./setup" import { - SendNibiCompiled__factory, - TestERC20Compiled__factory, -} from "../types/ethers-contracts" -import { alice, deployERC20, sendTestNibi } from "./utils" + INTRINSIC_TX_GAS, + TENPOW12, + alice, + deployContractTestERC20, + deployContractSendNibi, + hexify, + sendTestNibi, + COMMON_TX_ARGS, +} from "./utils" describe("Basic Queries", () => { + jest.setTimeout(15e3) + it("Simple transfer, balance check", async () => { const amountToSend = toBigInt(5e12) * toBigInt(1e6) // unibi const senderBalanceBefore = await provider.getBalance(account) @@ -22,11 +29,14 @@ describe("Basic Queries", () => { expect(senderBalanceBefore).toBeGreaterThan(0) expect(recipientBalanceBefore).toEqual(BigInt(0)) + const tenPow12 = toBigInt(1e12) + // Execute EVM transfer - const transaction = { + const transaction: TransactionRequest = { gasLimit: toBigInt(100e3), to: alice, value: amountToSend, + maxFeePerGas: tenPow12, } const txResponse = await account.sendTransaction(transaction) await txResponse.wait(1, 10e3) @@ -36,7 +46,6 @@ describe("Basic Queries", () => { const recipientBalanceAfter = await provider.getBalance(alice) // Assert balances with logging - const tenPow12 = toBigInt(1e12) const gasUsed = 50000n // 50k gas for the transaction const txCostMicronibi = amountToSend / tenPow12 + gasUsed const txCostWei = txCostMicronibi * tenPow12 @@ -49,7 +58,7 @@ describe("Basic Queries", () => { }) expect(senderBalanceAfter).toEqual(expectedSenderWei) expect(recipientBalanceAfter).toEqual(amountToSend) - }, 20e3) + }) it("eth_accounts", async () => { const accounts = await provider.listAccounts() @@ -64,6 +73,7 @@ describe("Basic Queries", () => { } const estimatedGas = await provider.estimateGas(tx) expect(estimatedGas).toBeGreaterThan(BigInt(0)) + expect(estimatedGas).toEqual(INTRINSIC_TX_GAS) }) it("eth_feeHistory", async () => { @@ -86,6 +96,7 @@ describe("Basic Queries", () => { it("eth_gasPrice", async () => { const gasPrice = await provider.send("eth_gasPrice", []) expect(gasPrice).toBeDefined() + expect(gasPrice).toEqual(hexify(1)) }) it("eth_getBalance", async () => { @@ -133,9 +144,7 @@ describe("Basic Queries", () => { }) it("eth_getCode", async () => { - const factory = new SendNibiCompiled__factory(account) - const contract = await factory.deploy() - await contract.waitForDeployment() + const contract = await deployContractSendNibi() const contractAddr = await contract.getAddress() const code = await provider.send("eth_getCode", [contractAddr, "latest"]) expect(code).toBeDefined() @@ -143,7 +152,7 @@ describe("Basic Queries", () => { it("eth_getFilterChanges", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -154,7 +163,11 @@ describe("Basic Queries", () => { expect(filterId).toBeDefined() // Execute some contract TX - const tx = await contract.transfer(alice, parseEther("0.01")) + const tx = await contract.transfer( + alice, + parseEther("0.01"), + COMMON_TX_ARGS, + ) await tx.wait(1, 5e3) await new Promise((resolve) => setTimeout(resolve, 3000)) @@ -167,12 +180,12 @@ describe("Basic Queries", () => { const success = await provider.send("eth_uninstallFilter", [filterId]) expect(success).toBeTruthy() - }, 20e3) + }) // Skipping as the method is not implemented it.skip("eth_getFilterLogs", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -192,12 +205,12 @@ describe("Basic Queries", () => { expect(changes[0]).toHaveProperty("address") expect(changes[0]).toHaveProperty("data") expect(changes[0]).toHaveProperty("topics") - }, 20e3) + }) // Skipping as the method is not implemented it.skip("eth_getLogs", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -212,11 +225,11 @@ describe("Basic Queries", () => { expect(changes[0]).toHaveProperty("address") expect(changes[0]).toHaveProperty("data") expect(changes[0]).toHaveProperty("topics") - }, 20e3) + }) it("eth_getProof", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const slot = 1 // Assuming balanceOf is at slot 1 @@ -243,12 +256,12 @@ describe("Basic Queries", () => { expect(proof.storageProof[0]).toHaveProperty("value") expect(proof.storageProof[0]).toHaveProperty("proof") } - }, 20e3) + }) // Skipping as the method is not implemented it.skip("eth_getLogs", async () => { // Deploy ERC-20 contract - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const filter = { fromBlock: "latest", @@ -264,10 +277,10 @@ describe("Basic Queries", () => { expect(logs[0]).toHaveProperty("address") expect(logs[0]).toHaveProperty("data") expect(logs[0]).toHaveProperty("topics") - }, 20e3) + }) it("eth_getProof", async () => { - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const slot = 1 // Assuming balanceOf is at slot 1 @@ -294,15 +307,15 @@ describe("Basic Queries", () => { expect(proof.storageProof[0]).toHaveProperty("value") expect(proof.storageProof[0]).toHaveProperty("proof") } - }, 20e3) + }) it("eth_getStorageAt", async () => { - const contract = await deployERC20() + const contract = await deployContractTestERC20() const contractAddr = await contract.getAddress() const value = await provider.getStorage(contractAddr, 1) expect(value).toBeDefined() - }, 20e3) + }) it("eth_getTransactionByBlockHashAndIndex, eth_getTransactionByBlockNumberAndIndex", async () => { // Execute EVM transfer @@ -329,16 +342,16 @@ describe("Basic Queries", () => { expect(txByBlockNumber["from"]).toEqual(txByBlockHash["from"]) expect(txByBlockNumber["to"]).toEqual(txByBlockHash["to"]) expect(txByBlockNumber["value"]).toEqual(txByBlockHash["value"]) - }, 20e3) + }) it("eth_getTransactionByHash", async () => { const txResponse = await sendTestNibi() const txByHash = await provider.getTransaction(txResponse.hash) expect(txByHash).toBeDefined() expect(txByHash.hash).toEqual(txResponse.hash) - }, 20e3) + }) - it("eth_getTransactionByHash", async () => { + it("eth_getTransactionCount", async () => { const txCount = await provider.getTransactionCount(account.address) expect(txCount).toBeGreaterThanOrEqual(0) }) @@ -348,7 +361,7 @@ describe("Basic Queries", () => { const txReceipt = await provider.getTransactionReceipt(txResponse.hash) expect(txReceipt).toBeDefined() expect(txReceipt.hash).toEqual(txResponse.hash) - }, 20e3) + }) it("eth_getUncleCountByBlockHash", async () => { const latestBlock = await provider.getBlockNumber() diff --git a/e2e/evm/test/contract_infinite_loop_gas.test.ts b/e2e/evm/test/contract_infinite_loop_gas.test.ts index 15ed369e6..34ea36f33 100644 --- a/e2e/evm/test/contract_infinite_loop_gas.test.ts +++ b/e2e/evm/test/contract_infinite_loop_gas.test.ts @@ -1,18 +1,15 @@ import { describe, expect, it } from "@jest/globals" import { toBigInt } from "ethers" -import { InfiniteLoopGasCompiled__factory } from "../types/ethers-contracts" -import { account } from "./setup" +import { COMMON_TX_ARGS, deployContractInfiniteLoopGas } from "./utils" describe("Infinite loop gas contract", () => { it("should fail due to out of gas error", async () => { - const factory = new InfiniteLoopGasCompiled__factory(account) - const contract = await factory.deploy() - await contract.waitForDeployment() + const contract = await deployContractInfiniteLoopGas() expect(contract.counter()).resolves.toBe(toBigInt(0)) try { - const tx = await contract.forever({ gasLimit: 1e6 }) + const tx = await contract.forever({ gasLimit: 1e6, ...COMMON_TX_ARGS }) await tx.wait() throw new Error("The transaction should have failed but did not.") } catch (error) { diff --git a/e2e/evm/test/contract_send_nibi.test.ts b/e2e/evm/test/contract_send_nibi.test.ts index 6dcf7967e..16edb35d5 100644 --- a/e2e/evm/test/contract_send_nibi.test.ts +++ b/e2e/evm/test/contract_send_nibi.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from "@jest/globals" import { toBigInt, Wallet } from "ethers" import { account, provider } from "./setup" -import { deploySendReceiveNibi } from "./utils" +import { COMMON_TX_ARGS, deployContractSendNibi } from "./utils" describe("Send NIBI via smart contract", () => { it("should send via transfer method", async () => { - const contract = await deploySendReceiveNibi() + const contract = await deployContractSendNibi() const recipient = Wallet.createRandom() const weiToSend = toBigInt(5e12) * toBigInt(1e6) // 5 micro NIBI @@ -15,6 +15,7 @@ describe("Send NIBI via smart contract", () => { const tx = await contract.sendViaTransfer(recipient, { value: weiToSend, + ...COMMON_TX_ARGS, }) const receipt = await tx.wait(1, 5e3) @@ -35,7 +36,7 @@ describe("Send NIBI via smart contract", () => { }, 20e3) it("should send via send method", async () => { - const contract = await deploySendReceiveNibi() + const contract = await deployContractSendNibi() const recipient = Wallet.createRandom() const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBi @@ -45,6 +46,7 @@ describe("Send NIBI via smart contract", () => { const tx = await contract.sendViaSend(recipient, { value: weiToSend, + ...COMMON_TX_ARGS, }) const receipt = await tx.wait(1, 5e3) @@ -65,7 +67,7 @@ describe("Send NIBI via smart contract", () => { }, 20e3) it("should send via transfer method", async () => { - const contract = await deploySendReceiveNibi() + const contract = await deployContractSendNibi() const recipient = Wallet.createRandom() const weiToSend = toBigInt(100e12) * toBigInt(1e6) // 100 NIBI @@ -75,6 +77,7 @@ describe("Send NIBI via smart contract", () => { const tx = await contract.sendViaCall(recipient, { value: weiToSend, + ...COMMON_TX_ARGS, }) const receipt = await tx.wait(1, 5e3) diff --git a/e2e/evm/test/erc20.test.ts b/e2e/evm/test/erc20.test.ts index b5d87c3f7..9a4082e5d 100644 --- a/e2e/evm/test/erc20.test.ts +++ b/e2e/evm/test/erc20.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from "@jest/globals" import { parseUnits, toBigInt, Wallet } from "ethers" import { account } from "./setup" -import { deployERC20 } from "./utils" +import { COMMON_TX_ARGS, deployContractTestERC20 } from "./utils" describe("ERC-20 contract tests", () => { it("should send properly", async () => { - const contract = await deployERC20() + const contract = await deployContractTestERC20() expect(contract.getAddress()).resolves.toBeDefined() const ownerInitialBalance = parseUnits("1000000", 18) @@ -16,7 +16,7 @@ describe("ERC-20 contract tests", () => { // send to alice const amountToSend = parseUnits("1000", 18) // contract tokens - let tx = await contract.transfer(alice, amountToSend) + let tx = await contract.transfer(alice, amountToSend, COMMON_TX_ARGS) await tx.wait() expect(contract.balanceOf(account)).resolves.toEqual( diff --git a/e2e/evm/test/utils.ts b/e2e/evm/test/utils.ts index 6029685b3..efb82d0dd 100644 --- a/e2e/evm/test/utils.ts +++ b/e2e/evm/test/utils.ts @@ -1,34 +1,53 @@ import { + InfiniteLoopGasCompiled__factory, SendNibiCompiled__factory, TestERC20Compiled__factory, } from "../types/ethers-contracts" import { account } from "./setup" -import { parseEther, toBigInt, Wallet } from "ethers" +import { parseEther, toBigInt, TransactionRequest, Wallet } from "ethers" -const alice = Wallet.createRandom() +export const alice = Wallet.createRandom() -const deployERC20 = async () => { +export const hexify = (x: number): string => { + return "0x" + x.toString(16) +} + +/** 10 to the power of 12 */ +export const TENPOW12 = toBigInt(1e12) + +export const COMMON_TX_ARGS: TransactionRequest = { maxFeePerGas: TENPOW12 } + +export const INTRINSIC_TX_GAS: bigint = 21000n + +export const deployContractTestERC20 = async () => { const factory = new TestERC20Compiled__factory(account) - const contract = await factory.deploy() + const contract = await factory.deploy({ maxFeePerGas: TENPOW12 }) await contract.waitForDeployment() return contract } -const deploySendReceiveNibi = async () => { + +export const deployContractSendNibi = async () => { const factory = new SendNibiCompiled__factory(account) - const contract = await factory.deploy() + const contract = await factory.deploy({ maxFeePerGas: TENPOW12 }) await contract.waitForDeployment() return contract } -const sendTestNibi = async () => { - const transaction = { +export const deployContractInfiniteLoopGas = async () => { + const factory = new InfiniteLoopGasCompiled__factory(account) + const contract = await factory.deploy({ maxFeePerGas: TENPOW12 }) + await contract.waitForDeployment() + return contract +} + +export const sendTestNibi = async () => { + const transaction: TransactionRequest = { gasLimit: toBigInt(100e3), to: alice, value: parseEther("0.01"), + maxFeePerGas: TENPOW12, } const txResponse = await account.sendTransaction(transaction) await txResponse.wait(1, 10e3) return txResponse } - -export { alice, deployERC20, deploySendReceiveNibi, sendTestNibi } diff --git a/eth/rpc/backend/call_tx.go b/eth/rpc/backend/call_tx.go index 7f61bf880..d7cff6eb4 100644 --- a/eth/rpc/backend/call_tx.go +++ b/eth/rpc/backend/call_tx.go @@ -148,6 +148,7 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { } txHash := ethereumTx.AsTransaction().Hash() + b.logger.Debug("eth_sendRawTransaction", "txHash", txHash.Hex()) syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) @@ -291,7 +292,9 @@ func (b *Backend) SetTxDefaults(args evm.JsonTxArgs) (evm.JsonTxArgs, error) { } // EstimateGas returns an estimate of gas usage for the given smart contract call. -func (b *Backend) EstimateGas(args evm.JsonTxArgs, blockNrOptional *rpc.BlockNumber) (hexutil.Uint64, error) { +func (b *Backend) EstimateGas( + args evm.JsonTxArgs, blockNrOptional *rpc.BlockNumber, +) (hexutil.Uint64, error) { blockNr := rpc.EthPendingBlockNumber if blockNrOptional != nil { blockNr = *blockNrOptional diff --git a/eth/rpc/backend/call_tx_test.go b/eth/rpc/backend/call_tx_test.go index c10039f8a..879438d51 100644 --- a/eth/rpc/backend/call_tx_test.go +++ b/eth/rpc/backend/call_tx_test.go @@ -305,21 +305,21 @@ func (s *BackendSuite) TestSendRawTransaction() { expPass bool }{ { - "fail - empty bytes", + "sad - empty bytes", func() {}, []byte{}, common.Hash{}, false, }, { - "fail - no RLP encoded bytes", + "sad - no RLP encoded bytes", func() {}, bz, common.Hash{}, false, }, { - "fail - unprotected transactions", + "sad - unprotected transactions", func() { queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) s.backend.allowUnprotectedTxs = false @@ -330,7 +330,7 @@ func (s *BackendSuite) TestSendRawTransaction() { false, }, { - "fail - failed to get evm params", + "sad - failed to get evm params", func() { queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) s.backend.allowUnprotectedTxs = true @@ -341,7 +341,7 @@ func (s *BackendSuite) TestSendRawTransaction() { false, }, { - "fail - failed to broadcast transaction", + "sad - failed to broadcast transaction", func() { client := s.backend.clientCtx.Client.(*mocks.Client) queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) diff --git a/eth/rpc/backend/tx_info.go b/eth/rpc/backend/tx_info.go index 5691617ff..48bbbf2bf 100644 --- a/eth/rpc/backend/tx_info.go +++ b/eth/rpc/backend/tx_info.go @@ -213,6 +213,8 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{ return nil, errors.New("can't find index of ethereum tx") } + // TODO: refactor(evm-rpc-backend): Replace interface with gethcore.Receipt + // in eth_getTransactionReceipt receipt := map[string]interface{}{ // Consensus fields: These fields are defined by the Yellow Paper "status": status, diff --git a/eth/rpc/rpc.go b/eth/rpc/rpc.go index 4ca1d8393..8bd080c61 100644 --- a/eth/rpc/rpc.go +++ b/eth/rpc/rpc.go @@ -168,7 +168,7 @@ func NewRPCTxFromMsg( return NewRPCTxFromEthTx(tx, blockHash, blockNumber, index, baseFee, chainID) } -// NewTransactionFromData returns a transaction that will serialize to the RPC +// NewRPCTxFromEthTx returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func NewRPCTxFromEthTx( tx *gethcore.Transaction, diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index cac42bea1..89fce0a8d 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/types" geth "github.com/ethereum/go-ethereum" @@ -19,9 +20,9 @@ import ( "github.com/NibiruChain/nibiru/v2/app/appconst" "github.com/NibiruChain/nibiru/v2/eth" + "github.com/NibiruChain/nibiru/v2/eth/rpc/rpcapi" "github.com/NibiruChain/nibiru/v2/gosdk" - nibirucommon "github.com/NibiruChain/nibiru/v2/x/common" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" @@ -43,8 +44,10 @@ type TestSuite struct { suite.Suite cfg testnetwork.Config network *testnetwork.Network + val *testnetwork.Validator ethClient *ethclient.Client + ethAPI *rpcapi.EthAPI fundedAccPrivateKey *ecdsa.PrivateKey fundedAccEthAddr gethcommon.Address @@ -70,7 +73,9 @@ func (s *TestSuite) SetupSuite() { s.Require().NoError(err) s.network = network - s.ethClient = network.Validators[0].JSONRPCClient + s.val = network.Validators[0] + s.ethClient = s.val.JSONRPCClient + s.ethAPI = s.val.EthRPC_ETH s.contractData = embeds.SmartContract_TestERC20 testAccPrivateKey, _ := crypto.GenerateKey() @@ -78,10 +83,8 @@ func (s *TestSuite) SetupSuite() { s.fundedAccEthAddr = crypto.PubkeyToAddress(testAccPrivateKey.PublicKey) s.fundedAccNibiAddr = eth.EthAddrToNibiruAddr(s.fundedAccEthAddr) - val := s.network.Validators[0] - funds := sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 100_000_000)) // 10 NIBI - s.NoError(testnetwork.FillWalletFromValidator(s.fundedAccNibiAddr, funds, val, eth.EthBaseDenom)) + s.NoError(testnetwork.FillWalletFromValidator(s.fundedAccNibiAddr, funds, s.val, eth.EthBaseDenom)) s.NoError(s.network.WaitForNextBlock()) } @@ -109,9 +112,7 @@ func (s *TestSuite) Test_BlockByNumber() { ethBlock, err := s.ethClient.BlockByNumber(context.Background(), big.NewInt(networkBlockNumber)) s.NoError(err) - - // TODO: add more checks about the eth block - s.NotNil(ethBlock) + s.NoError(ethBlock.SanityCheck()) } // Test_BalanceAt EVM method: eth_getBalance @@ -240,7 +241,7 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { s.T().Logf("Sending %d wei to %s", weiToSend, recipientAddr.Hex()) signer := gethcore.LatestSignerForChainID(chainID) - gasPrice := big.NewInt(1) + gasPrice := evm.NativeToWei(big.NewInt(1)) tx, err := gethcore.SignNewTx( s.fundedAccPrivateKey, signer, @@ -249,7 +250,7 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { To: &recipientAddr, Value: weiToSend, Gas: params.TxGas, - GasPrice: gasPrice, + GasPrice: gasPrice, // 1 micronibi per gas }) s.NoError(err) err = s.ethClient.SendTransaction(context.Background(), tx) @@ -261,7 +262,7 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { costOfTx := new(big.Int).Add( weiToSend, - new(big.Int).Mul(evm.NativeToWei(new(big.Int).SetUint64(params.TxGas)), gasPrice), + new(big.Int).Mul((new(big.Int).SetUint64(params.TxGas)), gasPrice), ) wantSenderBalWei := new(big.Int).Sub(senderBalanceBeforeWei, costOfTx) s.Equal(wantSenderBalWei.String(), senderAmountAfterWei.String(), "surpising sender balance") @@ -271,6 +272,8 @@ func (s *TestSuite) Test_SimpleTransferTransaction() { s.Equal(weiToSend.String(), recipientBalanceAfter.String()) } +var blankCtx = context.Background() + // Test_SmartContract includes contract deployment, query, execution func (s *TestSuite) Test_SmartContract() { chainID, err := s.ethClient.ChainID(context.Background()) @@ -278,84 +281,116 @@ func (s *TestSuite) Test_SmartContract() { nonce, err := s.ethClient.NonceAt(context.Background(), s.fundedAccEthAddr, nil) s.NoError(err) - // Deploying contract - signer := gethcore.LatestSignerForChainID(chainID) - txData := s.contractData.Bytecode - tx, err := gethcore.SignNewTx( - s.fundedAccPrivateKey, - signer, - &gethcore.LegacyTx{ - Nonce: nonce, - Gas: 1_500_000, - GasPrice: big.NewInt(1), - Data: txData, - }) - s.NoError(err) - err = s.ethClient.SendTransaction(context.Background(), tx) - s.NoError(err) + s.T().Log("Make sure the account has funds.") + + funds := sdk.NewCoins(sdk.NewInt64Coin(eth.EthBaseDenom, 1_000_000_000)) + s.Require().NoError(testnetwork.FillWalletFromValidator( + s.fundedAccNibiAddr, funds, s.network.Validators[0], eth.EthBaseDenom), + ) s.NoError(s.network.WaitForNextBlock()) - hash := tx.Hash() - receipt, err := s.ethClient.TransactionReceipt(context.Background(), hash) - s.NoError(err) - contractAddress := receipt.ContractAddress - // Querying contract: owner's balance should be 1000_000 tokens - ownerInitialBalance := (&big.Int{}).Mul(big.NewInt(1000_000), nibirucommon.TO_ATTO) - s.assertERC20Balance(contractAddress, s.fundedAccEthAddr, ownerInitialBalance) + grpcUrl := s.network.Validators[0].AppConfig.GRPC.Address + grpcConn, err := gosdk.GetGRPCConnection(grpcUrl, true, 5) + s.NoError(err) - // Querying contract: recipient balance should be 0 - recipientAddr := gethcommon.BytesToAddress(testnetwork.NewAccount(s.network, "contract_recipient")) - s.assertERC20Balance(contractAddress, recipientAddr, big.NewInt(0)) + querier := bank.NewQueryClient(grpcConn) + resp, err := querier.Balance(context.Background(), &bank.QueryBalanceRequest{ + Address: s.fundedAccNibiAddr.String(), + Denom: eth.EthBaseDenom, + }) + s.Require().NoError(err) + // Expect 1.005 billion because of the setup function before this test. + s.True(resp.Balance.Amount.GT(math.NewInt(1_004_900_000)), "unexpectedly low balance ", resp.Balance.Amount.String()) - // Execute contract: send 1000 anibi to recipient - sendAmount := (&big.Int{}).Mul(big.NewInt(1000), nibirucommon.TO_ATTO) - input, err := s.contractData.ABI.Pack("transfer", recipientAddr, sendAmount) - s.NoError(err) - nonce, err = s.ethClient.NonceAt(context.Background(), s.fundedAccEthAddr, nil) - s.NoError(err) - tx, err = gethcore.SignNewTx( + s.T().Log("Deploy contract") + signer := gethcore.LatestSignerForChainID(chainID) + txData := s.contractData.Bytecode + tx, err := gethcore.SignNewTx( s.fundedAccPrivateKey, signer, &gethcore.LegacyTx{ - Nonce: nonce, - To: &contractAddress, - Gas: 1_500_000, - GasPrice: big.NewInt(1), - Data: input, + Nonce: nonce, + Gas: 100_500_000 + params.TxGasContractCreation, + GasPrice: evm.NativeToWei(new(big.Int).Add( + evm.BASE_FEE_MICRONIBI, big.NewInt(0), + )), + Data: txData, }) + s.Require().NoError(err) + + txBz, err := tx.MarshalBinary() s.NoError(err) - err = s.ethClient.SendTransaction(context.Background(), tx) - s.NoError(err) - s.NoError(s.network.WaitForNextBlock()) + txHash, err := s.ethAPI.SendRawTransaction(txBz) + s.Require().NoError(err) - // Querying contract: owner's balance should be 999_000 tokens - ownerBalance := (&big.Int{}).Mul(big.NewInt(999_000), nibirucommon.TO_ATTO) - s.assertERC20Balance(contractAddress, s.fundedAccEthAddr, ownerBalance) + s.T().Log("Assert: tx IS pending just after execution") + pendingTxs, err := s.ethAPI.GetPendingTransactions() + s.NoError(err) + s.Require().Len(pendingTxs, 1) + _ = s.network.WaitForNextBlock() + + s.T().Log("Assert: tx NOT pending") + { + wantCount := 0 + pending, err := s.ethClient.PendingTransactionCount(blankCtx) + s.NoError(err) + s.Require().EqualValues(uint(wantCount), pending) + + pendingTxs, err := s.ethAPI.GetPendingTransactions() + s.NoError(err) + s.Require().Len(pendingTxs, wantCount) + + // This query will succeed only if a receipt is found + _, err = s.ethClient.TransactionReceipt(blankCtx, txHash) + s.Require().Errorf(err, "receipt for txHash: %s", txHash.Hex()) + + // This query succeeds if no receipt is found + _, err = s.ethAPI.GetTransactionReceipt(txHash) + s.Require().NoError(err) + } - // Querying contract: recipient balance should be 1000 tokens - recipientBalance := (&big.Int{}).Mul(big.NewInt(1000), nibirucommon.TO_ATTO) - s.assertERC20Balance(contractAddress, recipientAddr, recipientBalance) + { + weiToSend := evm.NativeToWei(big.NewInt(1)) // 1 unibi + s.T().Logf("Sending %d wei (sanity check)", weiToSend) + accResp, err := s.val.EthRpcQueryClient.QueryClient.EthAccount(blankCtx, + &evm.QueryEthAccountRequest{ + Address: s.fundedAccEthAddr.Hex(), + }) + s.NoError(err) + nonce := accResp.Nonce + recipientAddr := gethcommon.BytesToAddress(testnetwork.NewAccount(s.network, "recipient")) + + signer := gethcore.LatestSignerForChainID(chainID) + gasPrice := evm.NativeToWei(big.NewInt(1)) + tx, err := gethcore.SignNewTx( + s.fundedAccPrivateKey, + signer, + &gethcore.LegacyTx{ + Nonce: nonce, + To: &recipientAddr, + Value: weiToSend, + Gas: params.TxGas, + GasPrice: gasPrice, // 1 micronibi per gas + }) + s.Require().NoError(err) + txBz, err := tx.MarshalBinary() + s.NoError(err) + txHash, err := s.ethAPI.SendRawTransaction(txBz) + // err = s.ethClient.SendTransaction(blankCtx, tx) + s.Require().NoError(err) + _ = s.network.WaitForNextBlock() + + txReceipt, err := s.ethClient.TransactionReceipt(blankCtx, txHash) + s.Require().NoError(err) + s.NotNil(txReceipt) + + logs, err := s.ethAPI.GetTransactionLogs(txHash) + s.NoError(err) + s.NotEmpty(logs) + } } func (s *TestSuite) TearDownSuite() { s.T().Log("tearing down integration test suite") s.network.Cleanup() } - -func (s *TestSuite) assertERC20Balance( - contractAddress gethcommon.Address, - userAddress gethcommon.Address, - expectedBalance *big.Int, -) { - input, err := s.contractData.ABI.Pack("balanceOf", userAddress) - s.NoError(err) - msg := geth.CallMsg{ - From: s.fundedAccEthAddr, - To: &contractAddress, - Data: input, - } - recipientBalanceBeforeBytes, err := s.ethClient.CallContract(context.Background(), msg, nil) - s.NoError(err) - balance := new(big.Int).SetBytes(recipientBalanceBeforeBytes) - s.Equal(expectedBalance.String(), balance.String()) -} diff --git a/eth/rpc/rpcapi/net_api_test.go b/eth/rpc/rpcapi/net_api_test.go new file mode 100644 index 000000000..2803a7e15 --- /dev/null +++ b/eth/rpc/rpcapi/net_api_test.go @@ -0,0 +1,13 @@ +package rpcapi_test + +import ( + "github.com/NibiruChain/nibiru/v2/app/appconst" +) + +func (s *TestSuite) TestNetNamespace() { + api := s.val.EthRpc_NET + s.Require().True(api.Listening()) + s.EqualValues( + appconst.GetEthChainID(s.val.ClientCtx.ChainID).String(), api.Version()) + s.Equal(0, api.PeerCount()) +} diff --git a/x/common/testutil/testnetwork/network.go b/x/common/testutil/testnetwork/network.go index 774824f9a..53509c4cf 100644 --- a/x/common/testutil/testnetwork/network.go +++ b/x/common/testutil/testnetwork/network.go @@ -98,30 +98,31 @@ func BuildNetworkConfig(appGenesis app.GenesisState) Config { chainID := "chain-" + tmrand.NewRand().Str(6) return Config{ - Codec: encCfg.Codec, - TxConfig: encCfg.TxConfig, - LegacyAmino: encCfg.Amino, - InterfaceRegistry: encCfg.InterfaceRegistry, AccountRetriever: authtypes.AccountRetriever{}, + AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), AppConstructor: NewAppConstructor(encCfg, chainID), - GenesisState: appGenesis, - TimeoutCommit: time.Second / 2, - ChainID: chainID, - NumValidators: 1, BondDenom: denoms.NIBI, + BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), + ChainID: chainID, + CleanupDir: true, + Codec: encCfg.Codec, + EnableTMLogging: false, // super noisy + GenesisState: appGenesis, + InterfaceRegistry: encCfg.InterfaceRegistry, + KeyringOptions: []keyring.Option{}, + LegacyAmino: encCfg.Amino, MinGasPrices: fmt.Sprintf("0.000006%s", denoms.NIBI), - AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), + NumValidators: 1, + PruningStrategy: types.PruningOptionNothing, + SigningAlgo: string(hd.Secp256k1Type), StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction), - BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), StartingTokens: sdk.NewCoins( sdk.NewCoin(denoms.NUSD, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), sdk.NewCoin(denoms.NIBI, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), sdk.NewCoin(denoms.USDC, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), ), - PruningStrategy: types.PruningOptionNothing, - CleanupDir: true, - SigningAlgo: string(hd.Secp256k1Type), - KeyringOptions: []keyring.Option{}, + TimeoutCommit: time.Second / 2, + TxConfig: encCfg.TxConfig, } } @@ -262,12 +263,11 @@ func New(logger Logger, baseDir string, cfg Config) (network *Network, err error appCfg.JSONRPC.API = serverconfig.GetAPINamespaces() } - loggerNoOp := log.NewNopLogger() + serverCtxLogger := log.NewNopLogger() if cfg.EnableTMLogging { - loggerNoOp = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + serverCtxLogger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) } - - ctx.Logger = loggerNoOp + ctx.Logger = serverCtxLogger nodeDirName := fmt.Sprintf("node%d", valIdx) nodeDir := filepath.Join(network.BaseDir, nodeDirName, "simd") @@ -454,7 +454,7 @@ func New(logger Logger, baseDir string, cfg Config) (network *Network, err error logger.Log("starting test network...") for idx, v := range network.Validators { - err := startInProcess(cfg, v) + err := startNodeAndServers(cfg, v) if err != nil { return nil, fmt.Errorf("failed to start node: %w", err) } diff --git a/x/common/testutil/testnetwork/start_node.go b/x/common/testutil/testnetwork/start_node.go new file mode 100644 index 000000000..dbdd5e53b --- /dev/null +++ b/x/common/testutil/testnetwork/start_node.go @@ -0,0 +1,171 @@ +package testnetwork + +import ( + "fmt" + "os" + "time" + + "cosmossdk.io/errors" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/NibiruChain/nibiru/v2/app/server" + ethrpc "github.com/NibiruChain/nibiru/v2/eth/rpc" + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" + "github.com/NibiruChain/nibiru/v2/eth/rpc/rpcapi" + + "github.com/cosmos/cosmos-sdk/server/api" + servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" + srvtypes "github.com/cosmos/cosmos-sdk/server/types" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cometbft/cometbft/node" + "github.com/cometbft/cometbft/p2p" + pvm "github.com/cometbft/cometbft/privval" + "github.com/cometbft/cometbft/proxy" + "github.com/cometbft/cometbft/rpc/client/local" +) + +func startNodeAndServers(cfg Config, val *Validator) error { + logger := val.Ctx.Logger + evmServerCtxLogger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + tmCfg := val.Ctx.Config + tmCfg.Instrumentation.Prometheus = false + + if err := val.AppConfig.ValidateBasic(); err != nil { + return err + } + + nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) + if err != nil { + return err + } + + app := cfg.AppConstructor(*val) + + genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg) + tmNode, err := node.NewNode( + tmCfg, + pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + genDocProvider, + node.DefaultDBProvider, + node.DefaultMetricsProvider(tmCfg.Instrumentation), + logger.With("module", val.Moniker), + ) + if err != nil { + return fmt.Errorf("failed to construct Node: %w", err) + } + + if err := tmNode.Start(); err != nil { + return fmt.Errorf("failed Node.Start(): %w", err) + } + + val.tmNode = tmNode + val.tmNode.Logger = logger + + if val.RPCAddress != "" { + val.RPCClient = local.New(tmNode) + } + + // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. + if val.APIAddress != "" || val.AppConfig.GRPC.Enable { + val.ClientCtx = val.ClientCtx. + WithClient(val.RPCClient) + + // Add the tx service in the gRPC router. + app.RegisterTxService(val.ClientCtx) + + // Add the tendermint queries service in the gRPC router. + app.RegisterTendermintService(val.ClientCtx) + } + + if val.APIAddress != "" { + apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server")) + app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) + + errCh := make(chan error) + + go func() { + if err := apiSrv.Start(val.AppConfig.Config); err != nil { + errCh <- err + } + }() + + select { + case err := <-errCh: + return err + case <-time.After(srvtypes.ServerStartTime): // assume server started successfully + } + + val.api = apiSrv + } + + if val.AppConfig.GRPC.Enable { + grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC) + if err != nil { + return err + } + + val.grpc = grpcSrv + + if val.AppConfig.GRPCWeb.Enable { + val.grpcWeb, err = servergrpc.StartGRPCWeb(grpcSrv, val.AppConfig.Config) + if err != nil { + return err + } + } + } + + val.Ctx.Logger = evmServerCtxLogger + + if val.AppConfig.JSONRPC.Enable && val.AppConfig.JSONRPC.Address != "" { + if val.Ctx == nil || val.Ctx.Viper == nil { + return fmt.Errorf("validator %s context is nil", val.Moniker) + } + + tmEndpoint := "/websocket" + tmRPCAddr := fmt.Sprintf("tcp://%s", val.AppConfig.GRPC.Address) + + val.Logger.Log("Set EVM indexer") + homeDir := val.Ctx.Config.RootDir + evmTxIndexer, err := server.OpenEVMIndexer( + val.Ctx, evmServerCtxLogger, val.ClientCtx, homeDir, + ) + if err != nil { + return err + } + val.EthTxIndexer = evmTxIndexer + + val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, nil) + if err != nil { + return errors.Wrap(err, "failed to start JSON-RPC server") + } + + address := fmt.Sprintf("http://%s", val.AppConfig.JSONRPC.Address) + + val.JSONRPCClient, err = ethclient.Dial(address) + if err != nil { + return fmt.Errorf("failed to dial JSON-RPC at address %s: %w", val.AppConfig.JSONRPC.Address, err) + } + } + + val.Logger.Log("Set up Ethereum JSON-RPC client objects") + val.EthRpcQueryClient = ethrpc.NewQueryClient(val.ClientCtx) + val.EthRpcBackend = backend.NewBackend( + val.Ctx, + val.Ctx.Logger, + val.ClientCtx, + val.AppConfig.JSONRPC.AllowUnprotectedTxs, + val.EthTxIndexer, + ) + + val.Logger.Log("Expose typed methods for each namespace") + val.EthRPC_ETH = rpcapi.NewImplEthAPI(val.Ctx.Logger, val.EthRpcBackend) + val.EthRpc_WEB3 = rpcapi.NewImplWeb3API() + val.EthRpc_NET = rpcapi.NewImplNetAPI(val.ClientCtx) + + val.Ctx.Logger = logger // set back to normal setting + + return nil +} diff --git a/x/common/testutil/testnetwork/util.go b/x/common/testutil/testnetwork/util.go index 0e48c3188..4ba1e082d 100644 --- a/x/common/testutil/testnetwork/util.go +++ b/x/common/testutil/testnetwork/util.go @@ -6,12 +6,8 @@ import ( "os" "path/filepath" "testing" - "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - - "github.com/NibiruChain/nibiru/v2/app/server" tmtypes "github.com/cometbft/cometbft/abci/types" sdkcodec "github.com/cosmos/cosmos-sdk/codec" @@ -21,19 +17,11 @@ import ( "github.com/NibiruChain/nibiru/v2/app/codec" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server/api" - servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" - srvtypes "github.com/cosmos/cosmos-sdk/server/types" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" tmos "github.com/cometbft/cometbft/libs/os" - "github.com/cometbft/cometbft/node" - "github.com/cometbft/cometbft/p2p" - pvm "github.com/cometbft/cometbft/privval" - "github.com/cometbft/cometbft/proxy" - "github.com/cometbft/cometbft/rpc/client/local" "github.com/cometbft/cometbft/types" tmtime "github.com/cometbft/cometbft/types/time" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -41,120 +29,6 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" ) -func startInProcess(cfg Config, val *Validator) error { - logger := val.Ctx.Logger - tmCfg := val.Ctx.Config - tmCfg.Instrumentation.Prometheus = false - - if err := val.AppConfig.ValidateBasic(); err != nil { - return err - } - - nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) - if err != nil { - return err - } - - app := cfg.AppConstructor(*val) - - genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg) - tmNode, err := node.NewNode( - tmCfg, - pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), - nodeKey, - proxy.NewLocalClientCreator(app), - genDocProvider, - node.DefaultDBProvider, - node.DefaultMetricsProvider(tmCfg.Instrumentation), - logger.With("module", val.Moniker), - ) - if err != nil { - return fmt.Errorf("failed to construct Node: %w", err) - } - - if err := tmNode.Start(); err != nil { - return fmt.Errorf("failed Node.Start(): %w", err) - } - - val.tmNode = tmNode - val.tmNode.Logger = logger - - if val.RPCAddress != "" { - val.RPCClient = local.New(tmNode) - } - - // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. - if val.APIAddress != "" || val.AppConfig.GRPC.Enable { - val.ClientCtx = val.ClientCtx. - WithClient(val.RPCClient) - - // Add the tx service in the gRPC router. - app.RegisterTxService(val.ClientCtx) - - // Add the tendermint queries service in the gRPC router. - app.RegisterTendermintService(val.ClientCtx) - } - - if val.APIAddress != "" { - apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server")) - app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) - - errCh := make(chan error) - - go func() { - if err := apiSrv.Start(val.AppConfig.Config); err != nil { - errCh <- err - } - }() - - select { - case err := <-errCh: - return err - case <-time.After(srvtypes.ServerStartTime): // assume server started successfully - } - - val.api = apiSrv - } - - if val.AppConfig.GRPC.Enable { - grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC) - if err != nil { - return err - } - - val.grpc = grpcSrv - - if val.AppConfig.GRPCWeb.Enable { - val.grpcWeb, err = servergrpc.StartGRPCWeb(grpcSrv, val.AppConfig.Config) - if err != nil { - return err - } - } - } - if val.AppConfig.JSONRPC.Enable && val.AppConfig.JSONRPC.Address != "" { - if val.Ctx == nil || val.Ctx.Viper == nil { - return fmt.Errorf("validator %s context is nil", val.Moniker) - } - - tmEndpoint := "/websocket" - tmRPCAddr := fmt.Sprintf("tcp://%s", val.AppConfig.GRPC.Address) - - val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, nil) - if err != nil { - return err - } - - address := fmt.Sprintf("http://%s", val.AppConfig.JSONRPC.Address) - - val.JSONRPCClient, err = ethclient.Dial(address) - if err != nil { - return fmt.Errorf("failed to dial JSON-RPC at %s: %w", val.AppConfig.JSONRPC.Address, err) - } - } - - return nil -} - func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error { genTime := tmtime.Now() diff --git a/x/common/testutil/testnetwork/validator_node.go b/x/common/testutil/testnetwork/validator_node.go index 6d851f4ce..bbdb53395 100644 --- a/x/common/testutil/testnetwork/validator_node.go +++ b/x/common/testutil/testnetwork/validator_node.go @@ -3,14 +3,20 @@ package testnetwork import ( "context" "fmt" + "math/big" "net/http" "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/suite" serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" + "github.com/NibiruChain/nibiru/v2/eth" + ethrpc "github.com/NibiruChain/nibiru/v2/eth/rpc" + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" + "github.com/NibiruChain/nibiru/v2/eth/rpc/rpcapi" "github.com/cometbft/cometbft/node" tmclient "github.com/cometbft/cometbft/rpc/client" @@ -20,6 +26,11 @@ import ( serverapi "github.com/cosmos/cosmos-sdk/server/api" sdk "github.com/cosmos/cosmos-sdk/types" "google.golang.org/grpc" + + geth "github.com/ethereum/go-ethereum" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" ) // Validator defines an in-process Tendermint validator node. Through this @@ -73,7 +84,14 @@ type Validator struct { // - rpc.Local RPCClient tmclient.Client - JSONRPCClient *ethclient.Client + JSONRPCClient *ethclient.Client + EthRpcQueryClient *ethrpc.QueryClient + EthRpcBackend *backend.Backend + EthTxIndexer eth.EVMTxIndexer + + EthRPC_ETH *rpcapi.EthAPI + EthRpc_WEB3 *rpcapi.APIWeb3 + EthRpc_NET *rpcapi.NetAPI Logger Logger @@ -228,3 +246,22 @@ func centerText(text string, width int) string { return fmt.Sprintf("%s%s%s", leftBuffer, text, rightBuffer) } + +func (val *Validator) AssertERC20Balance( + contract gethcommon.Address, + accAddr gethcommon.Address, + expectedBalance *big.Int, + s *suite.Suite, +) { + input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("balanceOf", accAddr) + s.NoError(err) + msg := geth.CallMsg{ + From: accAddr, + To: &contract, + Data: input, + } + recipientBalanceBeforeBytes, err := val.JSONRPCClient.CallContract(context.Background(), msg, nil) + s.NoError(err) + balance := new(big.Int).SetBytes(recipientBalanceBeforeBytes) + s.Equal(expectedBalance.String(), balance.String()) +}