From d55407fed6434163e051c127323a0f91c2784b6d Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 9 Jan 2024 23:52:50 +0700 Subject: [PATCH 1/3] add integration test util --- integration_test_util/README.md | 12 + integration_test_util/accounts.go | 185 +++ integration_test_util/accounts_test.go | 72 + integration_test_util/chain_suite.go | 668 ++++++++ integration_test_util/chain_suite_abci.go | 282 ++++ integration_test_util/chain_suite_bank.go | 142 ++ integration_test_util/chain_suite_contract.go | 432 +++++ integration_test_util/chain_suite_erc20.go | 95 ++ integration_test_util/chain_suite_gov.go | 114 ++ integration_test_util/chain_suite_ibc.go | 20 + integration_test_util/chain_suite_staking.go | 55 + .../chain_suite_tendermint.go | 15 + integration_test_util/chain_suite_tx.go | 197 +++ .../compiled_contracts/1-storage.json | 4 + .../compiled_contracts/1-storage.sol | 29 + .../compiled_contracts/2-wevmos.json | 4 + .../compiled_contracts/2-wevmos.sol | 534 +++++++ .../compiled_contracts/3-nft721.json | 4 + .../compiled_contracts/3-nft721.sol | 1389 +++++++++++++++++ .../compiled_contracts/4-nft1155.json | 4 + .../compiled_contracts/4-nft1155.sol | 993 ++++++++++++ .../compiled_contracts/5-create-Bar.json | 4 + .../5-create-BarInteraction.json | 4 + .../compiled_contracts/5-create-Foo.json | 4 + .../compiled_contracts/5-create.json | 4 + .../compiled_contracts/5-create.sol | 47 + .../demo/integration_test_demo_bank_test.go | 70 + .../integration_test_demo_contract_test.go | 157 ++ .../demo/integration_test_demo_erc20_test.go | 131 ++ .../demo/integration_test_demo_evm_test.go | 64 + .../demo/integration_test_demo_ibc_test.go | 89 ++ .../demo/integration_test_demo_rpc_test.go | 85 + .../integration_test_demo_staking_test.go | 39 + ...tegration_test_demo_tendermint_rpc_test.go | 19 + .../demo/integration_test_demo_test.go | 102 ++ integration_test_util/ibc_suite.go | 244 +++ integration_test_util/ibc_suite_tx.go | 46 + .../query_server_test_helper.go | 88 ++ integration_test_util/reactor.go | 34 + integration_test_util/types/accounts.go | 88 ++ integration_test_util/types/chain_app.go | 46 + integration_test_util/types/chain_app_imp.go | 42 + .../types/chain_app_imp_init.go | 363 +++++ .../types/chain_app_imp_keepers.go | 60 + integration_test_util/types/chain_config.go | 49 + integration_test_util/types/db/block.go | 32 + .../types/db/evm_internal_tx.go | 19 + integration_test_util/types/db/evm_log.go | 23 + integration_test_util/types/db/message.go | 41 + integration_test_util/types/db/message_ibc.go | 22 + .../types/db/smart_contract.go | 47 + integration_test_util/types/db/transaction.go | 30 + integration_test_util/types/keyring.go | 202 +++ integration_test_util/types/mem_db.go | 80 + integration_test_util/types/query_clients.go | 38 + .../types/response_deliver_eth_tx.go | 46 + integration_test_util/types/signer.go | 50 + integration_test_util/types/temporary.go | 44 + integration_test_util/types/tendermint_app.go | 9 + .../types/tendermint_app_imp.go | 49 + integration_test_util/utils/crypto.go | 12 + integration_test_util/utils/number.go | 28 + integration_test_util/utils/port.go | 70 + integration_test_util/utils/tendermint.go | 137 ++ 64 files changed, 8108 insertions(+) create mode 100644 integration_test_util/README.md create mode 100644 integration_test_util/accounts.go create mode 100644 integration_test_util/accounts_test.go create mode 100644 integration_test_util/chain_suite.go create mode 100644 integration_test_util/chain_suite_abci.go create mode 100644 integration_test_util/chain_suite_bank.go create mode 100644 integration_test_util/chain_suite_contract.go create mode 100644 integration_test_util/chain_suite_erc20.go create mode 100644 integration_test_util/chain_suite_gov.go create mode 100644 integration_test_util/chain_suite_ibc.go create mode 100644 integration_test_util/chain_suite_staking.go create mode 100644 integration_test_util/chain_suite_tendermint.go create mode 100644 integration_test_util/chain_suite_tx.go create mode 100644 integration_test_util/compiled_contracts/1-storage.json create mode 100644 integration_test_util/compiled_contracts/1-storage.sol create mode 100644 integration_test_util/compiled_contracts/2-wevmos.json create mode 100644 integration_test_util/compiled_contracts/2-wevmos.sol create mode 100644 integration_test_util/compiled_contracts/3-nft721.json create mode 100644 integration_test_util/compiled_contracts/3-nft721.sol create mode 100644 integration_test_util/compiled_contracts/4-nft1155.json create mode 100644 integration_test_util/compiled_contracts/4-nft1155.sol create mode 100644 integration_test_util/compiled_contracts/5-create-Bar.json create mode 100644 integration_test_util/compiled_contracts/5-create-BarInteraction.json create mode 100644 integration_test_util/compiled_contracts/5-create-Foo.json create mode 100644 integration_test_util/compiled_contracts/5-create.json create mode 100644 integration_test_util/compiled_contracts/5-create.sol create mode 100644 integration_test_util/demo/integration_test_demo_bank_test.go create mode 100644 integration_test_util/demo/integration_test_demo_contract_test.go create mode 100644 integration_test_util/demo/integration_test_demo_erc20_test.go create mode 100644 integration_test_util/demo/integration_test_demo_evm_test.go create mode 100644 integration_test_util/demo/integration_test_demo_ibc_test.go create mode 100644 integration_test_util/demo/integration_test_demo_rpc_test.go create mode 100644 integration_test_util/demo/integration_test_demo_staking_test.go create mode 100644 integration_test_util/demo/integration_test_demo_tendermint_rpc_test.go create mode 100644 integration_test_util/demo/integration_test_demo_test.go create mode 100644 integration_test_util/ibc_suite.go create mode 100644 integration_test_util/ibc_suite_tx.go create mode 100644 integration_test_util/query_server_test_helper.go create mode 100644 integration_test_util/reactor.go create mode 100644 integration_test_util/types/accounts.go create mode 100644 integration_test_util/types/chain_app.go create mode 100644 integration_test_util/types/chain_app_imp.go create mode 100644 integration_test_util/types/chain_app_imp_init.go create mode 100644 integration_test_util/types/chain_app_imp_keepers.go create mode 100644 integration_test_util/types/chain_config.go create mode 100644 integration_test_util/types/db/block.go create mode 100644 integration_test_util/types/db/evm_internal_tx.go create mode 100644 integration_test_util/types/db/evm_log.go create mode 100644 integration_test_util/types/db/message.go create mode 100644 integration_test_util/types/db/message_ibc.go create mode 100644 integration_test_util/types/db/smart_contract.go create mode 100644 integration_test_util/types/db/transaction.go create mode 100644 integration_test_util/types/keyring.go create mode 100644 integration_test_util/types/mem_db.go create mode 100644 integration_test_util/types/query_clients.go create mode 100644 integration_test_util/types/response_deliver_eth_tx.go create mode 100644 integration_test_util/types/signer.go create mode 100644 integration_test_util/types/temporary.go create mode 100644 integration_test_util/types/tendermint_app.go create mode 100644 integration_test_util/types/tendermint_app_imp.go create mode 100644 integration_test_util/utils/crypto.go create mode 100644 integration_test_util/utils/number.go create mode 100644 integration_test_util/utils/port.go create mode 100644 integration_test_util/utils/tendermint.go diff --git a/integration_test_util/README.md b/integration_test_util/README.md new file mode 100644 index 0000000000..142ecf15f6 --- /dev/null +++ b/integration_test_util/README.md @@ -0,0 +1,12 @@ +### Integration Test Suite utility +was created with aims to: +- Provide a simple way to set up and run integration tests +- Able to run integration tests in parallel +- Environment nearest to E2E testing +- Initialize a chain with CometBFT node to fully functional testing +- Init chain with pre-defined set of validators and wallets, easier to trace and debug +- Able to set up test for Json-RPC server + +Weak points: +- Only support Linux & MacOS, possible to make it compatible with Windows by enhance the TemporaryHolder functionality +- Easy to get import circle error, need a dedicated folder for integration test \ No newline at end of file diff --git a/integration_test_util/accounts.go b/integration_test_util/accounts.go new file mode 100644 index 0000000000..e768fd2f34 --- /dev/null +++ b/integration_test_util/accounts.go @@ -0,0 +1,185 @@ +package integration_test_util + +//goland:noinspection GoSnakeCaseUsage,SpellCheckingInspection +import ( + "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" + etherminthd "github.com/EscanBE/evermint/v12/crypto/hd" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + cosmoshd "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/stretchr/testify/require" + "testing" +) + +// newValidatorAccounts inits and return predefined validator accounts. +// By defining this, data in test cases will be more consistency. +func newValidatorAccounts(t *testing.T) itutiltypes.TestAccounts { + val1Account := newTestAccountFromMnemonic(t, IT_VAL_1_MNEMONIC) + val1Account.Type = itutiltypes.TestAccountTypeValidator + require.Equal(t, IT_VAL_1_VAL_ADDR, val1Account.GetValidatorAddress().String()) + require.Equal(t, IT_VAL_1_CONS_ADDR, val1Account.GetConsensusAddress().String()) + require.Equal(t, IT_VAL_1_ADDR, val1Account.GetCosmosAddress().String()) + + val2Account := newTestAccountFromMnemonic(t, IT_VAL_2_MNEMONIC) + val2Account.Type = itutiltypes.TestAccountTypeValidator + require.Equal(t, IT_VAL_2_VAL_ADDR, val2Account.GetValidatorAddress().String()) + require.Equal(t, IT_VAL_2_CONS_ADDR, val2Account.GetConsensusAddress().String()) + require.Equal(t, IT_VAL_2_ADDR, val2Account.GetCosmosAddress().String()) + + val3Account := newTestAccountFromMnemonic(t, IT_VAL_3_MNEMONIC) + val3Account.Type = itutiltypes.TestAccountTypeValidator + require.Equal(t, IT_VAL_3_VAL_ADDR, val3Account.GetValidatorAddress().String()) + require.Equal(t, IT_VAL_3_CONS_ADDR, val3Account.GetConsensusAddress().String()) + require.Equal(t, IT_VAL_3_ADDR, val3Account.GetCosmosAddress().String()) + + val4Account := newTestAccountFromMnemonic(t, IT_VAL_4_MNEMONIC) + val4Account.Type = itutiltypes.TestAccountTypeValidator + require.Equal(t, IT_VAL_4_VAL_ADDR, val4Account.GetValidatorAddress().String()) + require.Equal(t, IT_VAL_4_CONS_ADDR, val4Account.GetConsensusAddress().String()) + require.Equal(t, IT_VAL_4_ADDR, val4Account.GetCosmosAddress().String()) + + val5Account := newTestAccountFromMnemonic(t, IT_VAL_5_MNEMONIC) + val5Account.Type = itutiltypes.TestAccountTypeValidator + require.Equal(t, IT_VAL_5_VAL_ADDR, val5Account.GetValidatorAddress().String()) + require.Equal(t, IT_VAL_5_CONS_ADDR, val5Account.GetConsensusAddress().String()) + require.Equal(t, IT_VAL_5_ADDR, val5Account.GetCosmosAddress().String()) + + return []*itutiltypes.TestAccount{ + val1Account, + val2Account, + val3Account, + val4Account, + val5Account, + } +} + +// newWalletsAccounts inits and return predefined wallet accounts. +// By defining this, data in test cases will be more consistency. +func newWalletsAccounts(t *testing.T) itutiltypes.TestAccounts { + wal1Account := newTestAccountFromMnemonic(t, IT_WAL_1_MNEMONIC) + wal1Account.Type = itutiltypes.TestAccountTypeWallet + require.Equal(t, IT_WAL_1_ETH_ADDR, wal1Account.GetEthAddress().String()) + require.Equal(t, IT_WAL_1_ADDR, wal1Account.GetCosmosAddress().String()) + + wal2Account := newTestAccountFromMnemonic(t, IT_WAL_2_MNEMONIC) + wal2Account.Type = itutiltypes.TestAccountTypeWallet + require.Equal(t, IT_WAL_2_ETH_ADDR, wal2Account.GetEthAddress().String()) + require.Equal(t, IT_WAL_2_ADDR, wal2Account.GetCosmosAddress().String()) + + wal3Account := newTestAccountFromMnemonic(t, IT_WAL_3_MNEMONIC) + wal3Account.Type = itutiltypes.TestAccountTypeWallet + require.Equal(t, IT_WAL_3_ETH_ADDR, wal3Account.GetEthAddress().String()) + require.Equal(t, IT_WAL_3_ADDR, wal3Account.GetCosmosAddress().String()) + + wal4Account := newTestAccountFromMnemonic(t, IT_WAL_4_MNEMONIC) + wal4Account.Type = itutiltypes.TestAccountTypeWallet + require.Equal(t, IT_WAL_4_ETH_ADDR, wal4Account.GetEthAddress().String()) + require.Equal(t, IT_WAL_4_ADDR, wal4Account.GetCosmosAddress().String()) + + wal5Account := newTestAccountFromMnemonic(t, IT_WAL_5_MNEMONIC) + wal5Account.Type = itutiltypes.TestAccountTypeWallet + require.Equal(t, IT_WAL_5_ETH_ADDR, wal5Account.GetEthAddress().String()) + require.Equal(t, IT_WAL_5_ADDR, wal5Account.GetCosmosAddress().String()) + + return []*itutiltypes.TestAccount{ + wal1Account, + wal2Account, + wal3Account, + wal4Account, + wal5Account, + } +} + +// newTestAccountFromMnemonic creates a new test account from a mnemonic. +func newTestAccountFromMnemonic(t *testing.T, mnemonic string) *itutiltypes.TestAccount { + var algo keyring.SignatureAlgo + var err error + + //goland:noinspection SpellCheckingInspection + algo, err = keyring.NewSigningAlgoFromString("eth_secp256k1", supportedKeyringAlgorithms) + + derivedPriv, err := algo.Derive()(mnemonic, "", hdPath) + + privKey := algo.Generate()(derivedPriv) + require.NoError(t, err) + + priv := ðsecp256k1.PrivKey{ + Key: privKey.Bytes(), + } + + return NewTestAccount(t, priv) +} + +// NewTestAccount creates a new test account. If the private key is not provided, a new one will be generated. +func NewTestAccount(t *testing.T, nilAblePrivKey *ethsecp256k1.PrivKey) *itutiltypes.TestAccount { + testAccount := &itutiltypes.TestAccount{} + + var err error + + if nilAblePrivKey == nil { + nilAblePrivKey, err = ethsecp256k1.GenerateKey() + require.NoError(t, err) + require.NotNil(t, nilAblePrivKey) + } + + testAccount.PrivateKey = nilAblePrivKey + testAccount.Signer = itutiltypes.NewSigner(nilAblePrivKey) + + return testAccount +} + +var supportedKeyringAlgorithms = keyring.SigningAlgoList{etherminthd.EthSecp256k1, cosmoshd.Secp256k1} +var hdPath = cosmoshd.CreateHDPath(60, 0, 0).String() + +// TODO implement rename-chain compatible + +//goland:noinspection GoSnakeCaseUsage,SpellCheckingInspection +const ( + IT_VAL_1_ADDR = "evm1cqetlv987ntelz7s6ntvv95ltrns9qt6lqulcz" + IT_VAL_1_VAL_ADDR = "evmvaloper1cqetlv987ntelz7s6ntvv95ltrns9qt6et40np" + IT_VAL_1_CONS_ADDR = "evmvalcons1vv3kjxtrh7jredjehk5xw66r62euensst2lxka" + IT_VAL_1_MNEMONIC = "camera foster skate whisper faith opera axis false van urban clean pet shove census surface injury phone alley cup school pet edge trial pony" + + IT_VAL_2_ADDR = "evm19k6gu9tkr40uyhf86sjmlgy6hu4lpfx40p482t" + IT_VAL_2_VAL_ADDR = "evmvaloper19k6gu9tkr40uyhf86sjmlgy6hu4lpfx4f2uhpg" + IT_VAL_2_CONS_ADDR = "evmvalcons19fphsrnm2rx9jk4exfdeq46d6ptwlpy3w0cllv" + IT_VAL_2_MNEMONIC = "explain captain crucial fault symptom degree divorce beyond path security jewel alien beach finish bridge decide toast scene pelican sorry achieve off denial wall" + + IT_VAL_3_ADDR = "evm1rxczyg2x94dqcn77t4pyhcndg3r889dw9rn0uk" + IT_VAL_3_VAL_ADDR = "evmvaloper1rxczyg2x94dqcn77t4pyhcndg3r889dwrg6lh4" + IT_VAL_3_CONS_ADDR = "evmvalcons1vxky3ld4llhaqk8nl6pw6xkxqy97rwdarue89z" + IT_VAL_3_MNEMONIC = "worth talent fire announce file skull acquire ethics injury yard home list clap guard busy describe bag front grass noise index vacuum govern number" + + IT_VAL_4_ADDR = "evm1gmjvfd4pr0yd94t0x8xw4uwg2j0cn9g9hn0t6r" + IT_VAL_4_VAL_ADDR = "evmvaloper1gmjvfd4pr0yd94t0x8xw4uwg2j0cn9g93cxm3q" + IT_VAL_4_CONS_ADDR = "evmvalcons1yl9a7v952ejxju9fec6hqeuuku4372pncyvrv2" + IT_VAL_4_MNEMONIC = "question joke action slice mistake carbon virtual still culture push estate inhale true endless market flip hammer word lecture pen toddler lyrics creek regular" + + IT_VAL_5_ADDR = "evm1fpveqajjpt2emsfkr5xwp80074mkn38x777ezk" + IT_VAL_5_VAL_ADDR = "evmvaloper1fpveqajjpt2emsfkr5xwp80074mkn38xc4hff4" + IT_VAL_5_CONS_ADDR = "evmvalcons1p6n7qpnn5lqyyujzrp344drz228l3wx0y3fvt5" + IT_VAL_5_MNEMONIC = "tornado fuel drill critic indicate pool few wheat omit sight stage focus mountain amused neck surge post giant vague nut marine spoon fragile outdoor" +) + +//goland:noinspection GoSnakeCaseUsage,SpellCheckingInspection +const ( + IT_WAL_1_ADDR = "evm139mq752delxv78jvtmwxhasyrycufsvr5jkxf3" + IT_WAL_1_ETH_ADDR = "0x89760f514DCfCCCf1E4c5eDC6Bf6041931c4c183" + IT_WAL_1_MNEMONIC = "curtain hat remain song receive tower stereo hope frog cheap brown plate raccoon post reflect wool sail salmon game salon group glimpse adult shift" + + IT_WAL_2_ADDR = "evm1yxmxrj9zwrkc855zdt2fk83m0r63tcjuvyy9sq" + IT_WAL_2_ETH_ADDR = "0x21b661c8A270ed83D2826aD49b1E3B78F515E25C" + IT_WAL_2_MNEMONIC = "coral drink glow assist canyon ankle hole buffalo vendor foster void clip welcome slush cherry omit member legal account lunar often hen winter culture" + + IT_WAL_3_ADDR = "evm1v3uay5np5a93kpv80rfldxkhe32hxsdg8ya87c" + IT_WAL_3_ETH_ADDR = "0x6479D25261A74B1b058778d3F69Ad7cC557341A8" + IT_WAL_3_MNEMONIC = "depth skull anxiety weasel pulp interest seek junk trumpet orbit glance drink comfort much alarm during lady strong matrix enable write pledge alcohol buzz" + + IT_WAL_4_ADDR = "evm1zsdj9vsw44kk46fmnka7k76smsaxgh6ps9l8p0" + IT_WAL_4_ETH_ADDR = "0x141B22B20ead6d6AE93B9DBBeB7b50DC3A645F41" + IT_WAL_4_MNEMONIC = "author humble raise whisper allow appear typical release fossil address spy jazz damage runway spy gossip add embark wrap frost toe advice matrix laundry" + + IT_WAL_5_ADDR = "evm1862crydur2cpjww66dhfzcc26yglvrcsh8x7at" + IT_WAL_5_ETH_ADDR = "0x3E958191BC1AB01939DAD36e91630Ad111F60f10" + IT_WAL_5_MNEMONIC = "museum stumble kingdom impulse replace angle exercise trial spring sphere cube brief foil bridge dish earn practice surprise quantum hunt scale solve october scout" +) diff --git a/integration_test_util/accounts_test.go b/integration_test_util/accounts_test.go new file mode 100644 index 0000000000..b727ba46d3 --- /dev/null +++ b/integration_test_util/accounts_test.go @@ -0,0 +1,72 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + "encoding/hex" + "github.com/stretchr/testify/require" + "testing" +) + +//goland:noinspection SpellCheckingInspection +func Test_TestAccount_KeysAndAddresses(t *testing.T) { + t.Run("test validator account", func(t *testing.T) { + valAcc := newTestAccountFromMnemonic(t, IT_VAL_1_MNEMONIC) + + require.Equal(t, IT_VAL_1_VAL_ADDR, valAcc.GetValidatorAddress().String()) + + require.Equal(t, IT_VAL_1_CONS_ADDR, valAcc.GetConsensusAddress().String()) + + require.Equal(t, IT_VAL_1_ADDR, valAcc.GetCosmosAddress().String()) + + require.Equal( + t, + "EthPubKeySecp256k1{0213B178097B00B5E87CD81D4F02E0F6DBF4B10608CE4B409A630C7E837F973350}", + valAcc.GetPubKey().String(), + ) + + require.Equal( + t, + "PubKeyEd25519{EC78352B42A13C1938886E4242AD7F2DD39DA55B886ECA06B826D50008C7C086}", + valAcc.GetSdkPubKey().String(), + ) + + const tmPub = "6323691963BFA43CB659BDA8676B43D2B3CCCE10" + require.Equal( + t, + tmPub, + valAcc.GetTmPubKey().Address().String(), + ) + tmPrivKey := valAcc.GetTmPrivKey() + require.Equal( + t, + tmPub, + tmPrivKey.PubKey().Address().String(), + ) + + require.Equal( + t, + "671959de1da2c3860f59ee81c19eeba23f18f585e22c47e3dfe559970d473038ec78352b42a13c1938886e4242ad7f2dd39da55b886eca06b826d50008c7c086", + hex.EncodeToString(tmPrivKey.Bytes()), + ) + }) + + t.Run("test wallet account", func(t *testing.T) { + walAcc := newTestAccountFromMnemonic(t, IT_WAL_1_MNEMONIC) + + require.Equal(t, IT_WAL_1_ETH_ADDR, walAcc.GetEthAddress().String()) + + require.Equal(t, IT_WAL_1_ADDR, walAcc.GetCosmosAddress().String()) + + require.Equal( + t, + "EthPubKeySecp256k1{020826F89EFBC5E5CFC8930E9E976D12EFA79821BF89AF73B9DAE6DE41ACEF6DB3}", + walAcc.GetPubKey().String(), + ) + + require.Equal( + t, + "PubKeyEd25519{7570E4698A126B47D5E2698C74ED728F2DE06593AE72E1987FA25BCFCD46A4BA}", + walAcc.GetSdkPubKey().String(), + ) + }) +} diff --git a/integration_test_util/chain_suite.go b/integration_test_util/chain_suite.go new file mode 100644 index 0000000000..6a57e3ce43 --- /dev/null +++ b/integration_test_util/chain_suite.go @@ -0,0 +1,668 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + "cosmossdk.io/simapp/params" + "fmt" + chainapp "github.com/EscanBE/evermint/v12/app" + "github.com/EscanBE/evermint/v12/constants" + etherminthd "github.com/EscanBE/evermint/v12/crypto/hd" + "github.com/EscanBE/evermint/v12/encoding" + kvindexer "github.com/EscanBE/evermint/v12/indexer" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + rpcbackend "github.com/EscanBE/evermint/v12/rpc/backend" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + cdb "github.com/cometbft/cometbft-db" + "github.com/cometbft/cometbft/crypto/tmhash" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmversion "github.com/cometbft/cometbft/proto/tendermint/version" + httpclient "github.com/cometbft/cometbft/rpc/client/http" + jsonrpcclient "github.com/cometbft/cometbft/rpc/jsonrpc/client" + tmstate "github.com/cometbft/cometbft/state" + "github.com/cometbft/cometbft/store" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cometbft/cometbft/version" + "github.com/cosmos/cosmos-sdk/baseapp" + cosmosclient "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + cosmostxtypes "github.com/cosmos/cosmos-sdk/types/tx" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govlegacytypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "math" + "math/big" + "os" + "reflect" + "runtime" + "strconv" + "strings" + "sync" + "testing" + "time" + "unsafe" +) + +// ChainIntegrationTestSuite is a helper for Chain integration test. +type ChainIntegrationTestSuite struct { + t *testing.T + require *require.Assertions + muTest sync.RWMutex + mu sync.RWMutex + ibcSuite *ChainsIbcIntegrationTestSuite + historicalContext map[int64]sdk.Context + useKeyring bool + tempHolder *itutiltypes.TemporaryHolder + logger log.Logger + EncodingConfig params.EncodingConfig + ChainConstantsConfig itutiltypes.ChainConstantConfig + DB *itutiltypes.MemDB + TendermintApp itutiltypes.TendermintApp + ChainApp itutiltypes.ChainApp + ValidatorSet *tmtypes.ValidatorSet + CurrentContext sdk.Context // might be out-dated if Tendermint is used + ValidatorAccounts itutiltypes.TestAccounts + WalletAccounts itutiltypes.TestAccounts + ModuleAccounts map[string]authtypes.ModuleAccountI + QueryClients *itutiltypes.QueryClients + EvmTxIndexer *kvindexer.KVIndexer + RpcBackend *rpcbackend.Backend + EthSigner ethtypes.Signer + TestConfig itutiltypes.TestConfig +} + +// CreateChainIntegrationTestSuite initialize an integration test suite using default configuration. +func CreateChainIntegrationTestSuite(t *testing.T, r *require.Assertions) *ChainIntegrationTestSuite { + return CreateChainIntegrationTestSuiteFromChainConfig(t, r, IntegrationTestChain1, false) +} + +//goland:noinspection SpellCheckingInspection +var IntegrationTestChain1 = itutiltypes.ChainConfig{ + CosmosChainId: constants.TestnetFullChainId, + BaseDenom: constants.BaseDenom, + Bech32Prefix: constants.Bech32Prefix, + EvmChainId: constants.TestnetEIP155ChainId, +} + +//goland:noinspection SpellCheckingInspection +var IntegrationTestChain2 = itutiltypes.ChainConfig{ + CosmosChainId: "evmos_9000-4", + BaseDenom: constants.BaseDenom, // use this due to the ante handle validation + Bech32Prefix: "evmos", + EvmChainId: 9000, +} + +// CreateChainIntegrationTestSuiteFromChainConfig initialize an integration test suite from a given chain config. +func CreateChainIntegrationTestSuiteFromChainConfig(t *testing.T, r *require.Assertions, chainCfg itutiltypes.ChainConfig, disableTendermint bool) *ChainIntegrationTestSuite { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.Skip("Integration test suite only works on Linux and MacOS") + } + + //goland:noinspection SpellCheckingInspection + const balancePerAccount = 2 + + chainCfg.EvmChainIdBigInt = big.NewInt(chainCfg.EvmChainId) + + encodingCfg := encoding.MakeConfig(chainapp.ModuleBasics) + + //goland:noinspection SpellCheckingInspection + testConfig := itutiltypes.TestConfig{ + SecondaryDenomUnits: []banktypes.DenomUnit{ + { + Denom: "utwo", + Exponent: 6, + }, + { + Denom: "uthree", + Exponent: 8, + }, + }, + InitBalanceAmount: sdk.NewInt(int64(balancePerAccount * math.Pow10(18))), + DefaultFeeAmount: sdk.NewInt(int64(math.Pow10(16))), + DisableTendermint: disableTendermint, + } + + clientCtx := cosmosclient.Context{}. + WithChainID(chainCfg.CosmosChainId). + WithCodec(encodingCfg.Codec). + WithInterfaceRegistry(encodingCfg.InterfaceRegistry). + WithTxConfig(encodingCfg.TxConfig). + WithLegacyAmino(encodingCfg.Amino). + WithKeyringOptions(etherminthd.EthSecp256k1Option()) + + tempHolder := itutiltypes.NewTemporaryHolder() + + // Setup assertions + if r == nil { + r = require.New(t) + } + + // Setup Test accounts + + validatorAccounts := newValidatorAccounts(t) + if disableTendermint { + // no-op + } else { + // test tendermint use only one validator + validatorAccounts = []*itutiltypes.TestAccount{validatorAccounts.Number(1)} + } + + walletAccounts := newWalletsAccounts(t) + + // Init database + sharedDb := itutiltypes.WrapCometBftDB(cdb.NewMemDB()) + evmIndexerDb := cdb.NewMemDB() // use dedicated db for EVM Tx-Indexer to prevent data corruption + + // Setup chain app + genesisAccountBalance := sdk.NewCoins( + sdk.NewCoin(chainCfg.BaseDenom, testConfig.InitBalanceAmount), + ) + for _, secondaryDenomUnit := range testConfig.SecondaryDenomUnits { + genesisAccountBalance = genesisAccountBalance.Add( + sdk.NewCoin(secondaryDenomUnit.Denom, testConfig.InitBalanceAmount), + ) + } + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + app, tmApp, valSet := itutiltypes.NewChainApp(chainCfg, disableTendermint, testConfig, encodingCfg, sharedDb, validatorAccounts, walletAccounts, genesisAccountBalance, tempHolder, logger) + baseApp := app.BaseApp() + + header := createFirstBlockHeader( + chainCfg.CosmosChainId, + validatorAccounts.Number(1).GetConsensusAddress(), + ) + ctx := baseApp.NewContext(false, header) + + evmParams := app.EvmKeeper().GetParams(ctx) + evmParams.EvmDenom = chainCfg.BaseDenom + err := app.EvmKeeper().SetParams(ctx, evmParams) + require.NoError(t, err) + + // Setup validators + for _, validatorAccount := range validatorAccounts { + val, err := stakingtypes.NewValidator( + validatorAccount.GetValidatorAddress(), + validatorAccount.GetSdkPubKey(), + stakingtypes.Description{}, + ) + require.NoError(t, err) + + val = stakingkeeper.TestingUpdateValidator(app.StakingKeeper(), ctx, val, true) + err = app.DistributionKeeper().Hooks().AfterValidatorCreated(ctx, val.GetOperator()) + require.NoError(t, err) + err = app.StakingKeeper().SetValidatorByConsAddr(ctx, val) + require.NoError(t, err) + } + + result := &ChainIntegrationTestSuite{ + t: t, + require: r, + muTest: sync.RWMutex{}, + mu: sync.RWMutex{}, + historicalContext: make(map[int64]sdk.Context), + tempHolder: tempHolder, + logger: logger, + EncodingConfig: encodingCfg, + ChainConstantsConfig: itutiltypes.NewChainConstantConfig( + chainCfg.CosmosChainId, + chainCfg.BaseDenom, + constants.BaseDenomExponent, + ), + DB: sharedDb, + ChainApp: app, + TendermintApp: tmApp, + ValidatorSet: valSet, + CurrentContext: ctx, + ValidatorAccounts: validatorAccounts, + WalletAccounts: walletAccounts, + ModuleAccounts: make(map[string]authtypes.ModuleAccountI), + EvmTxIndexer: kvindexer.NewKVIndexer(evmIndexerDb, log.NewNopLogger(), clientCtx), + EthSigner: ethtypes.LatestSignerForChainID(chainCfg.EvmChainIdBigInt), + TestConfig: testConfig, + } + + if disableTendermint { + result.Commit() // Commit the initial block + } else { + time.Sleep(300 * time.Millisecond) + result.Commit() + } + + result.CreateAllQueryClientsAndRpcBackend() + + accounts, _ := result.QueryClients.Auth.ModuleAccounts(nil, &authtypes.QueryModuleAccountsRequest{}) + for _, acc := range accounts.Accounts { + var account authtypes.AccountI + err = encodingCfg.InterfaceRegistry.UnpackAny(acc, &account) + require.NoError(t, err) + moduleAccount, ok := account.(authtypes.ModuleAccountI) + require.True(t, ok) + result.ModuleAccounts[moduleAccount.GetName()] = moduleAccount + } + + return result +} + +func (suite *ChainIntegrationTestSuite) T() *testing.T { + suite.muTest.RLock() + defer suite.muTest.RUnlock() + return suite.t +} + +func (suite *ChainIntegrationTestSuite) Require() *require.Assertions { + suite.muTest.RLock() + defer suite.muTest.RUnlock() + return suite.require +} + +func (suite *ChainIntegrationTestSuite) UseKeyring() { + suite.muTest.Lock() + defer suite.muTest.Unlock() + suite.useKeyring = true +} + +// Cleanup cleans up the ChainIntegrationTestSuite. +// This method should be called after each test or suite, depends on the tactic you shut down the Integration chain. +func (suite *ChainIntegrationTestSuite) Cleanup() { + if suite == nil { + return + } + + if suite.HasTendermint() { + suite.TendermintApp.Shutdown() + } + + if suite.tempHolder != nil { + if tempFiles, anyTemp := suite.tempHolder.GetTempFiles(); anyTemp { + for _, file := range tempFiles { + err := os.RemoveAll(file) + if err != nil { + fmt.Println("Failed to remove temp file", file) + fmt.Println(err) + } + } + } + } +} + +// BaseApp returns the BaseApp instance of the Integrated chain. +func (suite *ChainIntegrationTestSuite) BaseApp() *baseapp.BaseApp { + return suite.ChainApp.BaseApp() +} + +// CreateAllQueryClientsAndRpcBackend creates all query clients and RPC backend instance at recent block height. +// This method should be called after each commit to refresh the query clients. +func (suite *ChainIntegrationTestSuite) CreateAllQueryClientsAndRpcBackend() { + suite.QueryClients = suite.QueryClientsAt(0) + suite.RpcBackend = suite.RpcBackendAt(0) +} + +// ContextAt returns the context at a given context block height. +func (suite *ChainIntegrationTestSuite) ContextAt(height int64) sdk.Context { + if height == 0 { + height = suite.GetLatestBlockHeight() + } + + if ctx, found := suite.historicalContext[height]; found { + return ctx + } + + qCtx, err := suite.createAppQueryContext(height, false) + suite.Require().NoError(err) + + return qCtx +} + +// createAppQueryContext returns the query context at a given context block height. +// Used as a helper method to create query context to adapt with older version of Cosmos-SDK BaseApp, +// which does not expose CreateQueryContext method. +func (suite *ChainIntegrationTestSuite) createAppQueryContext(height int64, prove bool) (sdk.Context, error) { + return suite.BaseApp().CreateQueryContext(height, prove) +} + +// QueryClientsAt returns the list of query client instance that connects to store data at a given context block height. +func (suite *ChainIntegrationTestSuite) QueryClientsAt(height int64) *itutiltypes.QueryClients { + var sdkContext sdk.Context + if suite.HasTendermint() { + if height == 0 { + height = suite.GetLatestBlockHeight() + } + sdkContext = suite.CurrentContext + if height > 0 { + var err error + sdkContext, err = suite.createAppQueryContext(height, false) + suite.Require().NoError(err) + } + } else if height == 0 || height == suite.GetLatestBlockHeight() { + // latest block + sdkContext = suite.CurrentContext + } else { + var err error + sdkContext, err = suite.createAppQueryContext(height, false) + suite.Require().NoError(err) + } + + queryHelper := NewQueryServerTestHelper(sdkContext, suite.ChainApp.InterfaceRegistry()) + + authtypes.RegisterQueryServer(queryHelper, suite.ChainApp.AccountKeeper()) + authQueryClient := authtypes.NewQueryClient(queryHelper) + + banktypes.RegisterQueryServer(queryHelper, suite.ChainApp.BankKeeper()) + bankQueryClient := banktypes.NewQueryClient(queryHelper) + + disttypes.RegisterQueryServer(queryHelper, distkeeper.NewQuerier(suite.ChainApp.DistributionKeeper())) + distributionQueryClient := disttypes.NewQueryClient(queryHelper) + + erc20types.RegisterQueryServer(queryHelper, suite.ChainApp.Erc20Keeper()) + erc20QueryClient := erc20types.NewQueryClient(queryHelper) + + evmtypes.RegisterQueryServer(queryHelper, suite.ChainApp.EvmKeeper()) + evmQueryClient := evmtypes.NewQueryClient(queryHelper) + + feemarkettypes.RegisterQueryServer(queryHelper, suite.ChainApp.FeeMarketKeeper()) + feeMarketQueryClient := feemarkettypes.NewQueryClient(queryHelper) + + govv1types.RegisterQueryServer(queryHelper, suite.ChainApp.GovKeeper()) + govV1QueryClient := govv1types.NewQueryClient(queryHelper) + + govlegacytypes.RegisterQueryServer(queryHelper, govkeeper.NewLegacyQueryServer(suite.ChainApp.GovKeeper())) + govLegacyQueryClient := govlegacytypes.NewQueryClient(queryHelper) + + ibctransfertypes.RegisterQueryServer(queryHelper, suite.ChainApp.IbcTransferKeeper()) + ibcTransferQueryClient := ibctransfertypes.NewQueryClient(queryHelper) + + slashingtypes.RegisterQueryServer(queryHelper, suite.ChainApp.SlashingKeeper()) + slashingQueryClient := slashingtypes.NewQueryClient(queryHelper) + + stakingtypes.RegisterQueryServer(queryHelper, stakingkeeper.Querier{Keeper: suite.ChainApp.StakingKeeper()}) + stakingQueryClient := stakingtypes.NewQueryClient(queryHelper) + + serviceClient := cosmostxtypes.NewServiceClient(queryHelper) + + rpcQueryClient := rpctypes.QueryClient{ + ServiceClient: serviceClient, + QueryClient: evmQueryClient, + FeeMarket: feeMarketQueryClient, + } + + var tendermintRpcHttpClient *httpclient.HTTP + if suite.HasTendermint() { + rpcAddr26657, supported := suite.TendermintApp.GetRpcAddr() + suite.Require().True(supported) + + httpClient26657, err := jsonrpcclient.DefaultHTTPClient(rpcAddr26657) + suite.Require().NoError(err) + + tendermintRpcHttpClient, err = httpclient.NewWithClient(rpcAddr26657, "/websocket", httpClient26657) + suite.Require().NoError(err) + + err = tendermintRpcHttpClient.Start() + suite.Require().NoError(err) + } + + clientQueryCtx := cosmosclient.Context{}. + WithChainID(suite.ChainConstantsConfig.GetCosmosChainID()). + WithCodec(suite.EncodingConfig.Codec). + WithInterfaceRegistry(suite.EncodingConfig.InterfaceRegistry). + WithTxConfig(suite.EncodingConfig.TxConfig). + WithLegacyAmino(suite.EncodingConfig.Amino). + WithKeyringOptions(etherminthd.EthSecp256k1Option()). + WithAccountRetriever(authtypes.AccountRetriever{}) + + if suite.useKeyring { + clientQueryCtx = clientQueryCtx.WithKeyring(itutiltypes.NewIntegrationTestKeyring(suite.WalletAccounts)) + } else { + clientQueryCtx = clientQueryCtx.WithKeyring(itutiltypes.NewIntegrationTestKeyring(nil)) + } + + if height > 0 { + clientQueryCtx = clientQueryCtx.WithHeight(height) + } + + if suite.HasTendermint() { + clientQueryCtx = clientQueryCtx.WithClient(tendermintRpcHttpClient) + } + + cosmostxtypes.RegisterServiceServer( + queryHelper, + authtx.NewTxServer(clientQueryCtx, suite.BaseApp().Simulate, suite.ChainApp.InterfaceRegistry()), + ) + + return &itutiltypes.QueryClients{ + GrpcConnection: queryHelper, + ClientQueryCtx: clientQueryCtx, + TendermintRpcHttpClient: tendermintRpcHttpClient, + Auth: authQueryClient, + Bank: bankQueryClient, + Distribution: distributionQueryClient, + Erc20: erc20QueryClient, + EVM: evmQueryClient, + GovV1: govV1QueryClient, + GovLegacy: govLegacyQueryClient, + IbcTransfer: ibcTransferQueryClient, + Slashing: slashingQueryClient, + Staking: stakingQueryClient, + ServiceClient: serviceClient, + Rpc: &rpcQueryClient, + } +} + +// RpcBackendAt returns the RPC-backend instance at a given context block height. +func (suite *ChainIntegrationTestSuite) RpcBackendAt(height int64) *rpcbackend.Backend { + queryClients := suite.QueryClientsAt(height) + rpcServerCtx := server.NewDefaultContext() + + rpcBackend := rpcbackend.NewBackend(rpcServerCtx, rpcServerCtx.Logger, queryClients.ClientQueryCtx, false, suite.EvmTxIndexer) + + // override the query client with the mock query client, for changing query context + getFieldQueryClient := func() reflect.Value { + return reflect.Indirect(reflect.ValueOf(rpcBackend).Elem()).FieldByName("queryClient") + } + fieldQueryClient := getFieldQueryClient() + reflect.NewAt(fieldQueryClient.Type(), unsafe.Pointer(fieldQueryClient.UnsafeAddr())). + Elem(). + Set(reflect.ValueOf(queryClients.Rpc)) + + return rpcBackend +} + +// GetLatestBlockHeight returns the most recent block height. +func (suite *ChainIntegrationTestSuite) GetLatestBlockHeight() int64 { + if suite.HasTendermint() { + // because Tendermint auto-commit blocks so the CurrentContext property might out-dated + return suite.BaseApp().LastBlockHeight() + } + + return suite.CurrentContext.BlockHeight() +} + +// WaitNextBlockOrCommit returns the most recent block height beside the following logic: +// +// - When Tendermint is Enabled, it waits for the next block to be committed before returning result. +// +// - When Tendermint is Disabled, it triggers commit block and starts a new block with an updated context. +// +// USE-CASE for this: you want to submit one or multiple txs and have sometime to know the executed block, +// while Tendermint auto commit blocks. +func (suite *ChainIntegrationTestSuite) WaitNextBlockOrCommit() int64 { + if !suite.HasTendermint() { + suite.Commit() + return suite.GetLatestBlockHeight() + } + + oldHeight := suite.GetLatestBlockHeight() + var currentHeight int64 + for { + currentHeight = suite.GetLatestBlockHeight() + if currentHeight > oldHeight { + break + } + time.Sleep(10 * time.Millisecond) + } + return currentHeight +} + +// Commit commits and starts a new block with an updated context. +func (suite *ChainIntegrationTestSuite) Commit() { + if suite.ibcSuite != nil { // ibc-connected chains must be committed together + suite.ibcSuite.CommitAllChains() + } else { + suite.commitAndBeginBlockAfter(1 * time.Hour) + } +} + +// ibcSuiteCommit is a helper function to commit with custom block time equals to IBC setup +func (suite *ChainIntegrationTestSuite) ibcSuiteCommit() { + suite.commitAndBeginBlockAfter(5 * time.Second) +} + +// commitAndBeginBlockAfter commits a block at a given time. +func (suite *ChainIntegrationTestSuite) commitAndBeginBlockAfter(t time.Duration) { + suite.mu.Lock() + defer suite.mu.Unlock() + + defer func() { + suite.CreateAllQueryClientsAndRpcBackend() + }() + + var newCtx sdk.Context + var newValSet *tmtypes.ValidatorSet + + if suite.HasTendermint() { + // awaiting next block generated by tendermint + originalHeight := suite.GetLatestBlockHeight() + var latestHeight int64 + for { + time.Sleep(10 * time.Millisecond) + latestHeight = suite.GetLatestBlockHeight() + if latestHeight > originalHeight { + break + } + } + + blockStore, stateStore := suite.GetBlockStoreAndStateStore() + + tmBlk := blockStore.LoadBlock(latestHeight) + valSet, err := stateStore.LoadValidators(latestHeight) + suite.Require().NoErrorf(err, "failed to load validator set for block %d", latestHeight) + + header := tmBlk.Header.ToProto() + ctx := suite.createNewContext(suite.CurrentContext, *header) + suite.triggerEvmIndexer(latestHeight, blockStore, stateStore) // trigger EVM Tx-Indexer indexing data to latest + + newCtx = ctx + newValSet = valSet + } else { + // manually commit block and move to next + backupContext := suite.CurrentContext + + nextCtx, nextValSet, err := suite.commitAndCreateNewCtx(suite.CurrentContext, t, suite.ValidatorSet) + suite.Require().NoError(err) + suite.Require().Equalf(suite.CurrentContext.BlockHeight()+1, nextCtx.BlockHeight(), "next block height must be increased by 1") + + suite.historicalContext[backupContext.BlockHeight()] = backupContext + + newCtx = nextCtx + newValSet = nextValSet + } + + suite.CurrentContext = newCtx + suite.ValidatorSet = newValSet +} + +// GetIbcTimeoutHeight returns a timeout height for IBC packet, based on recent block, plus the offset. +func (suite *ChainIntegrationTestSuite) GetIbcTimeoutHeight(offsetHeight int64) ibcclienttypes.Height { + chainId := suite.ChainConstantsConfig.GetCosmosChainID() + idx := strings.LastIndex(chainId, "-") + rev := chainId[idx+1:] + revInt, err := strconv.ParseUint(rev, 10, 64) + suite.Require().NoError(err) + return ibcclienttypes.NewHeight(revInt, uint64(suite.GetLatestBlockHeight()+offsetHeight)) +} + +// triggerEvmIndexer indexes EVM txs from blockStore and stateStore, upto latestHeight. +func (suite *ChainIntegrationTestSuite) triggerEvmIndexer(latestHeight int64, blockStore *store.BlockStore, stateStore tmstate.Store) { + suite.Require().NotZero(latestHeight) + suite.Require().NotNil(blockStore) + suite.Require().NotNil(stateStore) + + lastIndexedHeight, err := suite.EvmTxIndexer.LastIndexedBlock() + suite.Require().NoError(err) + + if lastIndexedHeight >= latestHeight { + return + } + + var indexFromBlock int64 = 1 + if lastIndexedHeight >= 0 { + indexFromBlock = lastIndexedHeight + 1 + } + + var ch int64 + for ch = indexFromBlock; ch <= latestHeight; ch++ { + tmBlk := blockStore.LoadBlock(ch) + tmAbciResponse, err := stateStore.LoadABCIResponses(ch) + suite.Require().NoErrorf(err, "failed to load abci response for block %d", ch) + err = suite.EvmTxIndexer.IndexBlock(tmBlk, tmAbciResponse.DeliverTxs) + suite.Require().NoErrorf(err, "failed to index block %d", ch) + } +} + +// GetBlockStoreAndStateStore returns blockStore and stateStore if Tendermint is Enabled. +// +// WARN: if Tendermint is Disabled, the call will panic. +func (suite *ChainIntegrationTestSuite) GetBlockStoreAndStateStore() (*store.BlockStore, tmstate.Store) { + suite.EnsureTendermint() + blockStore := store.NewBlockStore(suite.DB) + stateStore := tmstate.NewStore(suite.DB, tmstate.StoreOptions{ + DiscardABCIResponses: false, + }) + return blockStore, stateStore +} + +// createFirstBlockHeader creates a new Tendermint header, with context 1, for testing purposes. +func createFirstBlockHeader( + chainID string, + proposer sdk.ConsAddress, +) tmproto.Header { + //goland:noinspection SpellCheckingInspection + return tmproto.Header{ + ChainID: chainID, + Height: 1, + Time: time.Now().UTC(), + ValidatorsHash: nil, + AppHash: nil, + ProposerAddress: proposer.Bytes(), + Version: tmversion.Consensus{ + Block: version.BlockProtocol, + }, + LastBlockId: tmproto.BlockID{ + Hash: tmhash.Sum([]byte("block_id")), + PartSetHeader: tmproto.PartSetHeader{ + Total: 11, + Hash: tmhash.Sum([]byte("partset_header")), + }, + }, + DataHash: tmhash.Sum([]byte("data")), + NextValidatorsHash: tmhash.Sum([]byte("next_validators")), + ConsensusHash: tmhash.Sum([]byte("consensus")), + LastResultsHash: tmhash.Sum([]byte("last_result")), + EvidenceHash: tmhash.Sum([]byte("evidence")), + } +} diff --git a/integration_test_util/chain_suite_abci.go b/integration_test_util/chain_suite_abci.go new file mode 100644 index 0000000000..798058f5ee --- /dev/null +++ b/integration_test_util/chain_suite_abci.go @@ -0,0 +1,282 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + "context" + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + "fmt" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + abci "github.com/cometbft/cometbft/abci/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + coretypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/gogoproto/proto" + "github.com/pkg/errors" + "time" +) + +// commitAndCreateNewCtx commits a block at a given time, creating and return a new ctx for the next block +func (suite *ChainIntegrationTestSuite) commitAndCreateNewCtx(ctx sdk.Context, t time.Duration, vs *tmtypes.ValidatorSet) (sdk.Context, *tmtypes.ValidatorSet, error) { + header, nextVs, err := suite.commit(ctx, t, vs) + if err != nil { + return ctx, nil, err + } + + newCtx := suite.createNewContext(ctx, header) + + return newCtx, nextVs, nil +} + +// createNewContext returns a new sdk.Context with the same settings as the old one +func (suite *ChainIntegrationTestSuite) createNewContext(oldCtx sdk.Context, header tmproto.Header) sdk.Context { + // NewContext function keeps the multistore + // but resets other context fields + // GasMeter is set as InfiniteGasMeter + var newCtx sdk.Context + if suite.HasTendermint() { + newCtx = sdk.NewContext(suite.BaseApp().CommitMultiStore(), header, false, suite.BaseApp().Logger()) + } else { + newCtx = suite.BaseApp().NewContext(false, header) + } + // set the reset-ted fields to keep the current ctx settings + newCtx = newCtx.WithMinGasPrices(oldCtx.MinGasPrices()) + newCtx = newCtx.WithEventManager(oldCtx.EventManager()) + newCtx = newCtx.WithKVGasConfig(oldCtx.KVGasConfig()) + newCtx = newCtx.WithTransientKVGasConfig(oldCtx.TransientKVGasConfig()) + + return newCtx +} + +// DeliverTx delivers a Cosmos tx for a given set of msgs. +// The delivery mode is SYNC +func (suite *ChainIntegrationTestSuite) DeliverTx( + ctx sdk.Context, + signer *itutiltypes.TestAccount, + gasPrice *sdkmath.Int, + msgs ...sdk.Msg, +) (authsigning.Tx, abci.ResponseDeliverTx, error) { + suite.Require().NotNil(signer) + + tx, err := suite.PrepareCosmosTx( + ctx, + signer, + CosmosTxArgs{ + Gas: 10_000_000, + GasPrice: gasPrice, + Msgs: msgs, + }, + ) + if err != nil { + return nil, abci.ResponseDeliverTx{}, err + } + resDeliverTx, err := suite.BroadcastTx(tx) + return tx, resDeliverTx, err +} + +// DeliverTxAsync is the same as DeliverTx but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) DeliverTxAsync( + ctx sdk.Context, + signer *itutiltypes.TestAccount, + gasPrice *sdkmath.Int, + msgs ...sdk.Msg, +) (*coretypes.ResultBroadcastTx, error) { + suite.Require().NotNil(signer) + + tx, err := suite.PrepareCosmosTx( + ctx, + signer, + CosmosTxArgs{ + Gas: 10_000_000, + GasPrice: gasPrice, + Msgs: msgs, + }, + ) + if err != nil { + return nil, err + } + return suite.BroadcastTxAsync(tx) +} + +// DeliverEthTx generates and broadcasts MsgEthereumTx message populated within a Cosmos tx. +// The delivery mode is SYNC +func (suite *ChainIntegrationTestSuite) DeliverEthTx( + signer *itutiltypes.TestAccount, + ethMsg *evmtypes.MsgEthereumTx, +) (*itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(signer) + + tx, err := suite.PrepareEthTx(signer, ethMsg) + if err != nil { + return nil, err + } + responseDeliverTx, err := suite.BroadcastTx(tx) + if err != nil { + return nil, err + } + + res := itutiltypes.NewResponseDeliverEthTx(&responseDeliverTx) + + if _, err := checkEthTxResponse(responseDeliverTx, suite.EncodingConfig.Codec); err != nil { + return res, err + } + return res, nil +} + +// DeliverEthTxAsync is the same as DeliverEthTx but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) DeliverEthTxAsync( + account *itutiltypes.TestAccount, + ethMsg *evmtypes.MsgEthereumTx, +) error { + suite.Require().NotNil(account) + + tx, err := suite.PrepareEthTx(account, ethMsg) + if err != nil { + return err + } + _, err = suite.BroadcastTxAsync(tx) + return err +} + +// BroadcastTx does broadcast a tx over the network and returns the response +// The delivery mode is SYNC +func (suite *ChainIntegrationTestSuite) BroadcastTx(tx sdk.Tx) (responseDeliverTx abci.ResponseDeliverTx, err error) { + // bz are bytes to be broadcast over the network + var bz []byte + bz, err = suite.EncodingConfig.TxConfig.TxEncoder()(tx) + + if err == nil { + if suite.HasTendermint() { + res, err := suite.QueryClients.TendermintRpcHttpClient.BroadcastTxCommit(context.Background(), bz) + suite.Require().NoError(err) + responseDeliverTx = res.DeliverTx + } else { + responseDeliverTx = suite.BaseApp().DeliverTx( + abci.RequestDeliverTx{ + Tx: bz, + }, + ) + } + + if responseDeliverTx.Code != 0 { + err = errorsmod.Wrapf(errortypes.ErrInvalidRequest, responseDeliverTx.Log) + responseDeliverTx = abci.ResponseDeliverTx{} // purge + } + } + + return +} + +// BroadcastTxAsync is the same as BroadcastTx but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) BroadcastTxAsync(tx sdk.Tx) (resultBroadcastTx *coretypes.ResultBroadcastTx, err error) { + suite.EnsureTendermint() + // bz are bytes to be broadcast over the network + var bz []byte + bz, err = suite.EncodingConfig.TxConfig.TxEncoder()(tx) + + if err == nil { + res, err := suite.QueryClients.TendermintRpcHttpClient.BroadcastTxAsync(context.Background(), bz) + suite.Require().NoError(err) + resultBroadcastTx = res + } + + return +} + +// commit is helper function, it: +// +// - Runs the EndBlocker logic. +// +// - Commits the changes. +// +// - Updates the header. +// +// - Runs the BeginBlocker logic. +// +// - Finally, returns the updated header. +func (suite *ChainIntegrationTestSuite) commit(ctx sdk.Context, t time.Duration, vs *tmtypes.ValidatorSet) (tmproto.Header, *tmtypes.ValidatorSet, error) { + var nextVals *tmtypes.ValidatorSet + + baseApp := suite.BaseApp() + + header := ctx.BlockHeader() + + res := baseApp.EndBlock(abci.RequestEndBlock{ + Height: header.Height, + }) + + if vs != nil { + var err error + nextVals, err = applyValSetChanges(vs, res.ValidatorUpdates) + if err != nil { + return header, nil, err + } + header.ValidatorsHash = vs.Hash() + header.NextValidatorsHash = nextVals.Hash() + } + + _ = baseApp.Commit() + + header.Height++ + header.Time = header.Time.Add(t) + header.AppHash = baseApp.LastCommitID().Hash + + baseApp.BeginBlock(abci.RequestBeginBlock{ + Header: header, + }) + + return header, nextVals, nil +} + +// applyValSetChanges applies the validator set changes to the given validator set +func applyValSetChanges(valSet *tmtypes.ValidatorSet, valUpdates []abci.ValidatorUpdate) (*tmtypes.ValidatorSet, error) { + updates, err := tmtypes.PB2TM.ValidatorUpdates(valUpdates) + if err != nil { + return nil, err + } + + // must copy since validator set will mutate with UpdateWithChangeSet + newVals := valSet.Copy() + err = newVals.UpdateWithChangeSet(updates) + if err != nil { + return nil, err + } + + return newVals, nil +} + +func checkEthTxResponse(r abci.ResponseDeliverTx, cdc codec.Codec) ([]*evmtypes.MsgEthereumTxResponse, error) { + if !r.IsOK() { + return nil, fmt.Errorf("tx failed. Code: %d, Logs: %s", r.Code, r.Log) + } + + var txData sdk.TxMsgData + if err := cdc.Unmarshal(r.Data, &txData); err != nil { + return nil, err + } + + if len(txData.MsgResponses) == 0 { + return nil, fmt.Errorf("no message responses found") + } + + responses := make([]*evmtypes.MsgEthereumTxResponse, 0, len(txData.MsgResponses)) + for i := range txData.MsgResponses { + var res evmtypes.MsgEthereumTxResponse + if err := proto.Unmarshal(txData.MsgResponses[i].Value, &res); err != nil { + // TODO use corresponding proto for each chain + return nil, errors.Wrap(err, "failed to unmarshal proto") + } + + if res.Failed() { + return nil, fmt.Errorf("tx failed. VmError: %s", res.VmError) + } + responses = append(responses, &res) + } + + return responses, nil +} diff --git a/integration_test_util/chain_suite_bank.go b/integration_test_util/chain_suite_bank.go new file mode 100644 index 0000000000..f86588191e --- /dev/null +++ b/integration_test_util/chain_suite_bank.go @@ -0,0 +1,142 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/constants" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "math" +) + +// TxSend sends amount of base coin from one to another. +func (suite *ChainIntegrationTestSuite) TxSend(from, to *itutiltypes.TestAccount, amount float64) error { + _, _, err := suite.DeliverTx(suite.CurrentContext, from, nil, suite.buildBankSendMsg(from, to, amount)) + return err +} + +// TxSendAsync is the same as TxSend but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) TxSendAsync(from, to *itutiltypes.TestAccount, amount float64) error { + _, err := suite.DeliverTxAsync(suite.CurrentContext, from, nil, suite.buildBankSendMsg(from, to, amount)) + return err +} + +// buildBankSendMsg returns a MsgSend with the given parameters. +func (suite *ChainIntegrationTestSuite) buildBankSendMsg(from, to *itutiltypes.TestAccount, amount float64) *banktypes.MsgSend { + suite.Require().NotNil(from) + suite.Require().NotNil(to) + suite.Require().NotZero(amount) + + return &banktypes.MsgSend{ + FromAddress: from.GetCosmosAddress().String(), + ToAddress: to.GetCosmosAddress().String(), + Amount: sdk.Coins{ + sdk.Coin{ + Amount: sdk.NewInt(int64(amount * math.Pow10(constants.BaseDenomExponent))), + Denom: suite.ChainConstantsConfig.GetMinDenom(), + }, + }, + } +} + +// TxSendViaEVM sends amount of base coin from one to another, via EVM module +func (suite *ChainIntegrationTestSuite) TxSendViaEVM(from, to *itutiltypes.TestAccount, amount float64) (*evmtypes.MsgEthereumTx, error) { + msgEthereumTx := suite.buildMsgEthereumTxTransfer(from, to, amount) + _, err := suite.DeliverEthTx(from, msgEthereumTx) + return msgEthereumTx, err +} + +// TxSendViaEVMAsync is the same as TxSendViaEVM but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) TxSendViaEVMAsync(from, to *itutiltypes.TestAccount, amount float64) (*evmtypes.MsgEthereumTx, error) { + msgEthereumTx := suite.buildMsgEthereumTxTransfer(from, to, amount) + return msgEthereumTx, suite.DeliverEthTxAsync(from, msgEthereumTx) +} + +// buildMsgEthereumTxTransfer returns a MsgEthereumTx with the given parameters. +func (suite *ChainIntegrationTestSuite) buildMsgEthereumTxTransfer(from, to *itutiltypes.TestAccount, amount float64) *evmtypes.MsgEthereumTx { + suite.Require().NotNil(from) + suite.Require().NotNil(to) + suite.Require().NotZero(amount) + + toEvmAddr := to.GetEthAddress() + amountInt := sdk.NewInt(int64(amount * math.Pow10(constants.BaseDenomExponent))) + return suite.prepareMsgEthereumTx(suite.CurrentContext, from, &toEvmAddr, amountInt.BigInt(), nil, 21000) +} + +// QueryBalance returns the coin-base balance of given address at given context block. +// The data is read from query client. +func (suite *ChainIntegrationTestSuite) QueryBalance(height int64, cosmosAddress string) *sdk.Coin { + return suite.QueryBalanceByDenom(height, cosmosAddress, suite.ChainConstantsConfig.GetMinDenom()) +} + +// QueryBalanceByDenom returns the balance of specified denom of given address at given context block. +// The data is read from query client. +func (suite *ChainIntegrationTestSuite) QueryBalanceByDenom(height int64, cosmosAddress, baseDenom string) *sdk.Coin { + res, err := suite.QueryClientsAt(height).Bank.Balance( + rpctypes.ContextWithHeight(height), + &banktypes.QueryBalanceRequest{ + Address: cosmosAddress, + Denom: baseDenom, + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + return res.Balance +} + +// QueryBalanceFromStore returns the coin-base balance of given address at given context block. +// The data is read directly from store. +func (suite *ChainIntegrationTestSuite) QueryBalanceFromStore(height int64, address sdk.AccAddress) *sdk.Coin { + return suite.QueryBalanceByDenomFromStore(height, address, suite.ChainConstantsConfig.GetMinDenom()) +} + +// QueryBalanceByDenomFromStore returns the coin-base balance of a specific denom of given address at given context block. +// The data is read directly from store. +func (suite *ChainIntegrationTestSuite) QueryBalanceByDenomFromStore(height int64, address sdk.AccAddress, baseDenom string) *sdk.Coin { + coin := suite.ChainApp.BankKeeper().GetBalance(suite.ContextAt(height), address, baseDenom) + return &coin +} + +// MintCoin mints a new amount of coin into given account. +func (suite *ChainIntegrationTestSuite) MintCoin(receiver *itutiltypes.TestAccount, coin sdk.Coin) { + suite.Require().NotNil(receiver) + + suite.MintCoinToCosmosAddress(receiver.GetCosmosAddress(), coin) +} + +// MintCoinToCosmosAddress mints a new amount of coin into given account. +func (suite *ChainIntegrationTestSuite) MintCoinToCosmosAddress(receiver sdk.AccAddress, coin sdk.Coin) { + suite.Require().NotEmpty(receiver) + + coins := sdk.NewCoins(coin) + + err := suite.ChainApp.BankKeeper().MintCoins(suite.CurrentContext, minttypes.ModuleName, coins) + suite.Require().NoError(err) + + err = suite.ChainApp.BankKeeper().SendCoinsFromModuleToAccount(suite.CurrentContext, minttypes.ModuleName, receiver, coins) + suite.Require().NoError(err) +} + +// MintCoinToModuleAccount mints a new amount of coin into given module account. +func (suite *ChainIntegrationTestSuite) MintCoinToModuleAccount(receiver authtypes.ModuleAccountI, coin sdk.Coin) { + suite.Require().NotNil(receiver) + + coins := sdk.NewCoins(coin) + + err := suite.ChainApp.BankKeeper().MintCoins(suite.CurrentContext, minttypes.ModuleName, coins) + suite.Require().NoError(err) + + err = suite.ChainApp.BankKeeper().SendCoinsFromModuleToModule(suite.CurrentContext, minttypes.ModuleName, receiver.GetName(), coins) + suite.Require().NoError(err) +} + +// NewBaseCoin returns an instance of sdk.Coin of base coin with given amount. +func (suite *ChainIntegrationTestSuite) NewBaseCoin(amount int64) sdk.Coin { + intAmt := sdkmath.NewInt(amount).Mul(sdkmath.NewInt(int64(math.Pow10(constants.BaseDenomExponent)))) + return sdk.NewCoin(suite.ChainConstantsConfig.GetMinDenom(), intAmt) +} diff --git a/integration_test_util/chain_suite_contract.go b/integration_test_util/chain_suite_contract.go new file mode 100644 index 0000000000..29b92a6e93 --- /dev/null +++ b/integration_test_util/chain_suite_contract.go @@ -0,0 +1,432 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + sdkmath "cosmossdk.io/math" + _ "embed" // embed compiled smart contract + "encoding/json" + "github.com/EscanBE/evermint/v12/contracts" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "math" + "math/big" +) + +var ( + //go:embed compiled_contracts/1-storage.json + Contract1StorageJson []byte + + // Contract1Storage is the compiled storage contract + Contract1Storage evmtypes.CompiledContract + + //go:embed compiled_contracts/2-wevmos.json + Contract2WEvmosJson []byte + + // Contract2WEvmos is the compiled WEVMOS contract + Contract2WEvmos evmtypes.CompiledContract + + //go:embed compiled_contracts/3-nft721.json + Contract3Nft721Json []byte + + // Contract3Nft721 is the compiled NFT-721 contract + Contract3Nft721 evmtypes.CompiledContract + + //go:embed compiled_contracts/4-nft1155.json + Contract4Nft1155Json []byte + + // Contract4Nft1155 is the compiled NFT-1155 contract + Contract4Nft1155 evmtypes.CompiledContract + + //go:embed compiled_contracts/5-create-Foo.json + Contract5CreateFooJson []byte + //go:embed compiled_contracts/5-create-Bar.json + Contract5CreateBarJson []byte + //go:embed compiled_contracts/5-create-BarInteraction.json + Contract5CreateBarInteractionJson []byte + + // Contract5CreateFooContract is the compiled Foo-contract on 5-create.sol + Contract5CreateFooContract evmtypes.CompiledContract + // Contract5CreateBarContract is the compiled Bar-contract on 5-create.sol + Contract5CreateBarContract evmtypes.CompiledContract + // Contract5CreateBarInteractionContract is the compiled BarInteraction-contract on 5-create.sol + Contract5CreateBarInteractionContract evmtypes.CompiledContract +) + +func init() { + var err error + + // initialize embedded compiled contracts + + // 1-storage.sol + + err = json.Unmarshal(Contract1StorageJson, &Contract1Storage) + if err != nil { + panic(err) + } + + if len(Contract1Storage.Bin) == 0 { + panic("load contract failed") + } + + // 2-wevmos.sol + + err = json.Unmarshal(Contract2WEvmosJson, &Contract2WEvmos) + if err != nil { + panic(err) + } + + if len(Contract2WEvmos.Bin) == 0 { + panic("load contract failed") + } + + // 3-nft721.sol + + err = json.Unmarshal(Contract3Nft721Json, &Contract3Nft721) + if err != nil { + panic(err) + } + + if len(Contract3Nft721.Bin) == 0 { + panic("load contract failed") + } + + // 4-nft1155.sol + + err = json.Unmarshal(Contract4Nft1155Json, &Contract4Nft1155) + if err != nil { + panic(err) + } + + if len(Contract4Nft1155.Bin) == 0 { + panic("load contract failed") + } + + // 5-create.sol + + err = json.Unmarshal(Contract5CreateFooJson, &Contract5CreateFooContract) + if err != nil { + panic(err) + } + + if len(Contract5CreateFooContract.Bin) == 0 { + panic("load contract failed") + } + + err = json.Unmarshal(Contract5CreateBarJson, &Contract5CreateBarContract) + if err != nil { + panic(err) + } + + if len(Contract5CreateBarContract.Bin) == 0 { + panic("load contract failed") + } + + err = json.Unmarshal(Contract5CreateBarInteractionJson, &Contract5CreateBarInteractionContract) + if err != nil { + panic(err) + } + + if len(Contract5CreateBarInteractionContract.Bin) == 0 { + panic("load contract failed") + } +} + +// TxDeployErc20Contract deploys a new ERC20 contract with the given name, symbol and decimals. +// The given deployer will be used to deploy the contract. +func (suite *ChainIntegrationTestSuite) TxDeployErc20Contract(deployer *itutiltypes.TestAccount, name, symbol string, decimals uint8) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + contracts.ERC20MinterBurnerDecimalsContract, + name, symbol, decimals, + ) +} + +// TxDeploy1StorageContract deploys the embedded pre-compiled contract "1-storage.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy1StorageContract(deployer *itutiltypes.TestAccount) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract1Storage, + ) +} + +// TxDeploy2WEvmosContract deploys the embedded pre-compiled contract "2-wevmos.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy2WEvmosContract(deployer, rich *itutiltypes.TestAccount) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + if rich == nil { + rich = deployer + } + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract2WEvmos, + rich.GetEthAddress(), + ) +} + +// TxDeploy3Nft721Contract deploys the embedded pre-compiled contract "3-nft721.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy3Nft721Contract(deployer, rich *itutiltypes.TestAccount) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + if rich == nil { + rich = deployer + } + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract3Nft721, + rich.GetEthAddress(), + ) +} + +// TxDeploy4Nft1155Contract deploys the embedded pre-compiled contract "4-nft1155.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy4Nft1155Contract(deployer, rich *itutiltypes.TestAccount) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + if rich == nil { + rich = deployer + } + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract4Nft1155, + rich.GetEthAddress(), + ) +} + +// TxDeploy5CreateFooContract deploys the Foo contract within the embedded pre-compiled contract "5-create.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy5CreateFooContract(deployer *itutiltypes.TestAccount) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract5CreateFooContract, + ) +} + +// TxDeploy5CreateBarContract deploys the Bar contract within the embedded pre-compiled contract "5-create.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy5CreateBarContract(deployer *itutiltypes.TestAccount) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + return suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract5CreateBarContract, + ) +} + +// TxDeploy5CreateBarInteractionContract deploys the BarInteraction contract within the embedded pre-compiled contract "5-create.sol". +func (suite *ChainIntegrationTestSuite) TxDeploy5CreateBarInteractionContract(deployer *itutiltypes.TestAccount, contractBarAddress common.Address) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + addr, evmTx, res, err := suite.TxDeployContract( + suite.CurrentContext, + deployer, + Contract5CreateBarInteractionContract, + ) + suite.Commit() + if err != nil { + return common.Address{}, nil, nil, err + } + + data, err := Contract5CreateBarInteractionContract.ABI.Pack("setBarAddr", contractBarAddress) + suite.Require().NoError(err) + _, _, err = suite.TxSendEvmTx(suite.CurrentContext, deployer, &addr, nil, data) + if err != nil { + return common.Address{}, nil, nil, err + } + + return addr, evmTx, res, nil +} + +// TxDeployContract deploys the given compiled-contract with the given constructor arguments, using the given deployer. +func (suite *ChainIntegrationTestSuite) TxDeployContract(ctx sdk.Context, deployer *itutiltypes.TestAccount, contract evmtypes.CompiledContract, constructorArgs ...interface{}) (common.Address, *evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(deployer) + + nonce := suite.ChainApp.EvmKeeper().GetNonce(ctx, deployer.GetEthAddress()) + + ctorArgs, err := contract.ABI.Pack("", constructorArgs...) + if err != nil { + return common.Address{}, nil, nil, err + } + + data := append(contract.Bin, ctorArgs...) + + evmTx, resDeliver, err := suite.TxSendEvmTx(ctx, deployer, nil, nil, data) + if err != nil { + return common.Address{}, nil, nil, err + } + + return crypto.CreateAddress(deployer.GetEthAddress(), nonce), evmTx, resDeliver, nil +} + +// TxMintErc20Token call the "mint" function of the given ERC-20 contract, minting token to given account. +// Minter account will be used to call the function. +func (suite *ChainIntegrationTestSuite) TxMintErc20Token(contract common.Address, minter, mintTo *itutiltypes.TestAccount, amount uint16, decimals uint8) (*evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(minter) + suite.Require().NotNil(mintTo) + + mintAmt := computeAmount(amount, decimals) + data, err := contracts.ERC20MinterBurnerDecimalsContract.ABI.Pack("mint", mintTo.GetEthAddress(), mintAmt.BigInt()) + suite.Require().NoError(err) + return suite.TxSendEvmTx(suite.CurrentContext, minter, &contract, nil, data) +} + +// TxTransferErc20Token call the "transfer" function of the given ERC20 contract, transferring token from sender to receiver. +// Sender account will be used to call the function. +func (suite *ChainIntegrationTestSuite) TxTransferErc20Token(contract common.Address, sender, receiver *itutiltypes.TestAccount, amount uint16, decimals uint8) (*evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + + data := suite.prepareTransferErc20TokenData(receiver, amount, decimals) + return suite.TxSendEvmTx(suite.CurrentContext, sender, &contract, nil, data) +} + +// TxTransferErc20TokenAsync is the same as TxTransferErc20Token but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) TxTransferErc20TokenAsync(contract common.Address, sender, receiver *itutiltypes.TestAccount, amount uint16, decimals uint8) (*evmtypes.MsgEthereumTx, error) { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + + data := suite.prepareTransferErc20TokenData(receiver, amount, decimals) + return suite.TxSendEvmTxAsync(suite.CurrentContext, sender, &contract, nil, data) +} + +// prepareTransferErc20TokenData computes the call data for the "transfer" function of the given ERC-20 contract, with given amount of token to transfer. +func (suite *ChainIntegrationTestSuite) prepareTransferErc20TokenData(receiver *itutiltypes.TestAccount, amount uint16, decimals uint8) []byte { + suite.Require().NotNil(receiver) + transferAmt := computeAmount(amount, decimals) + data, err := contracts.ERC20MinterBurnerDecimalsContract.ABI.Pack("transfer", receiver.GetEthAddress(), transferAmt.BigInt()) + suite.Require().NoError(err) + return data +} + +// TxTransferNft721Token call the "safeTransferFrom" function of the given ERC-721 contract, transferring a NFT token from sender to receiver. +// Sender account will be used to call the function. +func (suite *ChainIntegrationTestSuite) TxTransferNft721Token(contract common.Address, abi abi.ABI, sender, receiver *itutiltypes.TestAccount, tokenId *big.Int) (*evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + suite.Require().NotNil(tokenId) + + data := suite.prepareTransferNft721TokenDara(abi, sender, receiver, tokenId) + return suite.TxSendEvmTx(suite.CurrentContext, sender, &contract, nil, data) +} + +// TxTransferNft721TokenAsync is the same as TxTransferNft721Token but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) TxTransferNft721TokenAsync(contract common.Address, abi abi.ABI, sender, receiver *itutiltypes.TestAccount, tokenId *big.Int) (*evmtypes.MsgEthereumTx, error) { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + suite.Require().NotNil(tokenId) + + data := suite.prepareTransferNft721TokenDara(abi, sender, receiver, tokenId) + return suite.TxSendEvmTxAsync(suite.CurrentContext, sender, &contract, nil, data) +} + +// prepareTransferNft721TokenDara computes the call data for the "safeTransferFrom" function of the given ERC-721 contract, with given tokenId of token to transfer. +func (suite *ChainIntegrationTestSuite) prepareTransferNft721TokenDara(abi abi.ABI, sender, receiver *itutiltypes.TestAccount, tokenId *big.Int) []byte { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + suite.Require().NotNil(tokenId) + + data, err := abi.Pack("safeTransferFrom", sender.GetEthAddress(), receiver.GetEthAddress(), tokenId) + suite.Require().NoError(err) + return data +} + +// TxTransferNft1155Token call the "safeTransferFrom" function of the given ERC-1155 contract, transferring give amount of given NFT token from sender to receiver. +func (suite *ChainIntegrationTestSuite) TxTransferNft1155Token(contract common.Address, abi abi.ABI, sender, receiver *itutiltypes.TestAccount, tokenId *big.Int, amount uint16) (*evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + suite.Require().NotNil(tokenId) + + data := suite.prepareTransferNft1155TokenData(abi, sender, receiver, tokenId, amount) + return suite.TxSendEvmTx(suite.CurrentContext, sender, &contract, nil, data) +} + +// TxTransferNft1155TokenAsync is the same as TxTransferNft1155Token but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) TxTransferNft1155TokenAsync(contract common.Address, abi abi.ABI, sender, receiver *itutiltypes.TestAccount, tokenId *big.Int, amount uint16) (*evmtypes.MsgEthereumTx, error) { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + suite.Require().NotNil(tokenId) + + data := suite.prepareTransferNft1155TokenData(abi, sender, receiver, tokenId, amount) + return suite.TxSendEvmTxAsync(suite.CurrentContext, sender, &contract, nil, data) +} + +// prepareTransferNft1155TokenData computes the call data for the "safeTransferFrom" function of the given ERC-1155 contract, with given tokenId and amount of token to transfer. +func (suite *ChainIntegrationTestSuite) prepareTransferNft1155TokenData(abi abi.ABI, sender, receiver *itutiltypes.TestAccount, tokenId *big.Int, amount uint16) []byte { + suite.Require().NotNil(sender) + suite.Require().NotNil(receiver) + suite.Require().NotNil(tokenId) + + amt := new(big.Int).SetInt64(int64(amount)) + + data, err := abi.Pack("safeTransferFrom", sender.GetEthAddress(), receiver.GetEthAddress(), tokenId, amt, []byte{}) + suite.Require().NoError(err) + return data +} + +// TxSendEvmTx builds and sends a MsgEthereumTx message based on the given call-data. +// The given sender account will be used to sign the message. +func (suite *ChainIntegrationTestSuite) TxSendEvmTx(ctx sdk.Context, sender *itutiltypes.TestAccount, to *common.Address, amount *big.Int, inputCallData []byte) (*evmtypes.MsgEthereumTx, *itutiltypes.ResponseDeliverEthTx, error) { + msgEthereumTx := suite.prepareMsgEthereumTx(ctx, sender, to, amount, inputCallData, 0) + resDeliverEthTx, err := suite.DeliverEthTx(sender, msgEthereumTx) + return msgEthereumTx, resDeliverEthTx, err +} + +// TxSendEvmTxAsync is the same as TxSendEvmTx but with Async delivery mode. +func (suite *ChainIntegrationTestSuite) TxSendEvmTxAsync(ctx sdk.Context, sender *itutiltypes.TestAccount, to *common.Address, amount *big.Int, inputCallData []byte) (*evmtypes.MsgEthereumTx, error) { + msgEthereumTx := suite.prepareMsgEthereumTx(ctx, sender, to, amount, inputCallData, 0) + err := suite.DeliverEthTxAsync(sender, msgEthereumTx) + return msgEthereumTx, err +} + +// prepareMsgEthereumTx builds a MsgEthereumTx message based on the given input data. +func (suite *ChainIntegrationTestSuite) prepareMsgEthereumTx(ctx sdk.Context, sender *itutiltypes.TestAccount, to *common.Address, amount *big.Int, inputCallData []byte, optionalGas uint64) *evmtypes.MsgEthereumTx { + suite.Require().NotNil(sender) + + from := sender.GetEthAddress() + + var gas uint64 = 6_000_000 + if optionalGas > 0 { + gas = optionalGas + } + + evmTxArgs := &evmtypes.EvmTxArgs{ + ChainID: suite.ChainApp.EvmKeeper().ChainID(), + Nonce: suite.ChainApp.EvmKeeper().GetNonce(ctx, from), + GasLimit: gas, + GasFeeCap: suite.ChainApp.FeeMarketKeeper().GetBaseFee(ctx), + GasTipCap: big.NewInt(1), + To: to, + Amount: amount, + Input: inputCallData, + Accesses: ðtypes.AccessList{}, + } + + msgEthereumTx := evmtypes.NewTx(evmTxArgs) + msgEthereumTx.From = from.String() + + return msgEthereumTx +} + +// computeAmount computes the amount of token to transfer, based on the given amount and decimals. +func computeAmount(amount uint16, decimals uint8) sdkmath.Int { + intDecimal := sdkmath.NewInt(int64(math.Pow10(int(decimals)))) + intAmount := sdkmath.NewInt(int64(amount)) + return intAmount.Mul(intDecimal) +} diff --git a/integration_test_util/chain_suite_erc20.go b/integration_test_util/chain_suite_erc20.go new file mode 100644 index 0000000000..781ef43d50 --- /dev/null +++ b/integration_test_util/chain_suite_erc20.go @@ -0,0 +1,95 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + "fmt" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/common" + "strings" +) + +// TxFullRegisterCoin registers a coin with the given denom and amount using Governance module. +// Auto propose a new proposal, vote and wait till it done. +func (suite *ChainIntegrationTestSuite) TxFullRegisterCoin(proposer *itutiltypes.TestAccount, minDenom string) uint64 { + suite.Require().NotNil(proposer) + + denomRes, err := suite.QueryClients.Bank.DenomMetadata(suite.CurrentContext, &banktypes.QueryDenomMetadataRequest{ + Denom: minDenom, + }) + suite.Require().NoError(err) + suite.Require().NotNilf(denomRes, "must register denom metadata for %s during integration setup for re-use purpose", minDenom) + + return suite.TxFullRegisterCoinByMetadata(proposer, denomRes.Metadata) +} + +// TxFullRegisterCoinWithNewBankMetadata registers a coin with the given denom metadata using Governance module. +// Auto propose a new proposal, vote and wait till it done. +func (suite *ChainIntegrationTestSuite) TxFullRegisterCoinWithNewBankMetadata(proposer *itutiltypes.TestAccount, minDenom, display string, exponent uint32) uint64 { + suite.Require().NotNil(proposer) + + return suite.TxFullRegisterCoinByMetadata(proposer, banktypes.Metadata{ + Description: fmt.Sprintf("Denom metadata of %s", minDenom), + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: minDenom, + Exponent: 0, + }, + { + Denom: display, + Exponent: exponent, + }, + }, + Base: minDenom, + Display: display, + Name: display, + Symbol: strings.ToUpper(display), + }) +} + +// TxFullRegisterCoinByMetadata registers a coin with the given denom metadata using Governance module. +// Auto propose a new proposal, vote and wait till it done. +func (suite *ChainIntegrationTestSuite) TxFullRegisterCoinByMetadata(proposer *itutiltypes.TestAccount, metadata banktypes.Metadata) uint64 { + suite.Require().NotNil(proposer) + + content := erc20types.NewRegisterCoinProposal( + fmt.Sprintf("Register ERC-20 token pairs for %s", metadata.Base), + fmt.Sprintf("Register ERC-20 token pairs for %s", metadata.Base), + metadata, + ) + + return suite.TxFullGov(proposer, content) +} + +// TxFullRegisterIbcCoinFromErc20Contract registers IBC coin for the given ERC-20 contract address using Governance module. +// Auto propose a new proposal, vote and wait till it done. +func (suite *ChainIntegrationTestSuite) TxFullRegisterIbcCoinFromErc20Contract(proposer *itutiltypes.TestAccount, erc20Address common.Address) uint64 { + suite.Require().NotNil(proposer) + + content := erc20types.NewRegisterERC20Proposal( + fmt.Sprintf("Register IBC token pairs for ERC-20 contract %s", erc20Address.String()), + fmt.Sprintf("Register IBC token pairs for ERC-20 contract %s", erc20Address.String()), + erc20Address.String(), + ) + + return suite.TxFullGov(proposer, content) +} + +// QueryFirstErc20TokenPair returns the first ERC-20 token pair available. +func (suite *ChainIntegrationTestSuite) QueryFirstErc20TokenPair(sourceErc20 bool) (erc20types.TokenPair, error) { + tokenPairs, err := suite.QueryClients.Erc20.TokenPairs(suite.CurrentContext, &erc20types.QueryTokenPairsRequest{}) + suite.Require().NoError(err) + suite.Require().NotNil(tokenPairs) + suite.Require().GreaterOrEqual(len(tokenPairs.TokenPairs), 1) + + for _, pair := range tokenPairs.TokenPairs { + if sourceErc20 && strings.HasPrefix(pair.Denom, "erc20/") { + return pair, nil + } else if !sourceErc20 && strings.HasPrefix(pair.Denom, "ibc/") { + return pair, nil + } + } + + return erc20types.TokenPair{}, fmt.Errorf("no token pair found for sourceErc20=%v", sourceErc20) +} diff --git a/integration_test_util/chain_suite_gov.go b/integration_test_util/chain_suite_gov.go new file mode 100644 index 0000000000..265eaba877 --- /dev/null +++ b/integration_test_util/chain_suite_gov.go @@ -0,0 +1,114 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypeslegacy "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "math" + "time" +) + +// TxFullGov submit gov proposal, full vote Yes and wait gov passed. +func (suite *ChainIntegrationTestSuite) TxFullGov(proposer *itutiltypes.TestAccount, newProposalContent govtypeslegacy.Content) uint64 { + suite.Require().NotNil(proposer) + + depositAmount := sdk.NewInt(int64(0.1 * math.Pow10(18))) + msg, err := govtypeslegacy.NewMsgSubmitProposal(newProposalContent, sdk.NewCoins( + sdk.NewCoin(suite.ChainConstantsConfig.GetMinDenom(), depositAmount), + ), proposer.GetCosmosAddress()) + suite.Require().NoError(err) + + _, _, err = suite.DeliverTx(suite.CurrentContext, proposer, nil, msg) + suite.Require().NoError(err) + + suite.Commit() + + proposal := suite.QueryLatestGovProposal(proposer) + suite.Require().NotNilf(proposal, "proposal could not be found") + + suite.Require().Equalf(govv1types.StatusVotingPeriod, proposal.Status, "proposal must be in voting period") + + suite.TxAllVote(proposal.Id, govv1types.OptionYes) + suite.Commit() + if suite.HasTendermint() { + time.Sleep(itutiltypes.TendermintGovVotingPeriod + 200*time.Millisecond) + } + + var proposalById *govv1types.Proposal + + for proposalById == nil || proposalById.Status == govv1types.StatusDepositPeriod || proposalById.Status == govv1types.StatusVotingPeriod { + proposalById = suite.QueryGovProposalById(proposal.Id) + suite.Commit() + } + + suite.Require().Equalf(govv1types.StatusPassed, proposalById.Status, "proposal must be passed") + + return proposal.Id +} + +// TxVote submits a vote on given proposal. +func (suite *ChainIntegrationTestSuite) TxVote(voter *itutiltypes.TestAccount, proposalId uint64, option govv1types.VoteOption) error { + suite.Require().NotNil(voter) + + _, _, err := suite.DeliverTx(suite.CurrentContext, voter, nil, &govv1types.MsgVote{ + ProposalId: proposalId, + Voter: voter.GetCosmosAddress().String(), + Option: option, + }) + + return err +} + +// TxAllVote using all accounts, each submits a vote on given proposal. +func (suite *ChainIntegrationTestSuite) TxAllVote(proposalId uint64, option govv1types.VoteOption) { + voted := make(map[string]bool) + + for _, voter := range append(suite.WalletAccounts, suite.ValidatorAccounts...) { + if voted[voter.GetCosmosAddress().String()] { + continue + } + err := suite.TxVote(voter, proposalId, option) + suite.Require().NoErrorf(err, "voter %s could not vote", voter.GetCosmosAddress().String()) + voted[voter.GetCosmosAddress().String()] = true + } + + return +} + +// QueryLatestGovProposal returns the latest gov proposal submitted by given proposer. +func (suite *ChainIntegrationTestSuite) QueryLatestGovProposal(proposer *itutiltypes.TestAccount) *govv1types.Proposal { + suite.Require().NotNil(proposer) + + resProposals, err := suite.QueryClients.GovV1.Proposals(suite.CurrentContext, &govv1types.QueryProposalsRequest{ + Depositor: proposer.GetCosmosAddress().String(), + }) + suite.Require().NoError(err) + suite.Require().NotNilf(resProposals, "proposal could not be found") + if len(resProposals.Proposals) < 1 { + return nil + } + if len(resProposals.Proposals) == 1 { + return resProposals.Proposals[0] + } + + var latestProposal *govv1types.Proposal + for _, proposal := range resProposals.Proposals { + if latestProposal == nil || latestProposal.Id < proposal.Id { + latestProposal = proposal + } + } + return latestProposal +} + +// QueryGovProposalById returns the gov proposal of the given proposal id. +func (suite *ChainIntegrationTestSuite) QueryGovProposalById(id uint64) *govv1types.Proposal { + resProposal, err := suite.QueryClients.GovV1.Proposal(suite.CurrentContext, &govv1types.QueryProposalRequest{ + ProposalId: id, + }) + suite.Require().NoError(err) + suite.Require().NotNilf(resProposal, "proposal could not be found") + suite.Require().NotNilf(resProposal.Proposal, "proposal could not be found") + return resProposal.Proposal +} diff --git a/integration_test_util/chain_suite_ibc.go b/integration_test_util/chain_suite_ibc.go new file mode 100644 index 0000000000..fa1ac9079c --- /dev/null +++ b/integration_test_util/chain_suite_ibc.go @@ -0,0 +1,20 @@ +package integration_test_util + +import ( + "fmt" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + "strings" +) + +// QueryDenomHash returns the denom hash of given denom trace information. +func (suite *ChainIntegrationTestSuite) QueryDenomHash(port, channel, denom string) string { + denomHashRes, err := suite.QueryClients.IbcTransfer.DenomHash(suite.CurrentContext, &ibctransfertypes.QueryDenomHashRequest{ + Trace: fmt.Sprintf("%s/%s/%s", port, channel, denom), + }) + suite.Require().NoError(err) + suite.Require().NotNil(denomHashRes) + suite.Require().NotEmpty(denomHashRes.Hash) + suite.Require().Falsef(strings.HasPrefix(denomHashRes.Hash, "ibc/"), "denom hash %s can not has prefix ibc/") + suite.Require().Equalf(strings.ToUpper(denomHashRes.Hash), denomHashRes.Hash, "denom hash %s must be all uppercase") + return fmt.Sprintf("ibc/%s", denomHashRes.Hash) +} diff --git a/integration_test_util/chain_suite_staking.go b/integration_test_util/chain_suite_staking.go new file mode 100644 index 0000000000..341bf3a4da --- /dev/null +++ b/integration_test_util/chain_suite_staking.go @@ -0,0 +1,55 @@ +package integration_test_util + +import ( + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// TxPrepareContextWithdrawDelegatorAndValidatorReward prepares context for withdraw delegator and validator reward. +// It does create delegation, allocate reward, commit state and wait a few blocks for reward to increase. +func (suite *ChainIntegrationTestSuite) TxPrepareContextWithdrawDelegatorAndValidatorReward(delegator *itutiltypes.TestAccount, delegate uint8, waitXBlocks uint8) (valAddr sdk.ValAddress) { + validatorAddr := suite.GetValidatorAddress(1) + + val := suite.ChainApp.StakingKeeper().Validator(suite.CurrentContext, validatorAddr) + + valReward := suite.NewBaseCoin(1) + delegationAmount := suite.NewBaseCoin(int64(delegate)) + + distAcc := suite.ChainApp.DistributionKeeper().GetDistributionAccount(suite.CurrentContext) + suite.MintCoinToModuleAccount(distAcc, suite.NewBaseCoin(int64(int(delegate)*int(10+waitXBlocks)))) + suite.ChainApp.AccountKeeper().SetModuleAccount(suite.CurrentContext, distAcc) + + suite.MintCoin(delegator, delegationAmount) + + suite.Commit() + + msgDelegate := &stakingtypes.MsgDelegate{ + DelegatorAddress: delegator.GetCosmosAddress().String(), + ValidatorAddress: validatorAddr.String(), + Amount: delegationAmount, + } + _, _, err := suite.DeliverTx(suite.CurrentContext, delegator, nil, msgDelegate) + suite.Require().NoError(err) + suite.Commit() + + for c := 1; c <= int(waitXBlocks); c++ { + suite.ChainApp.DistributionKeeper().AllocateTokensToValidator(suite.CurrentContext, val, sdk.NewDecCoinsFromCoins(valReward)) + suite.Commit() + } + + return validatorAddr +} + +// GetValidatorAddress returns the validator address of the validator with the given number. +// Due to there is a bug that the validator address is delivered from tendermint pubkey instead of cosmos pubkey in tendermint mode. +// So this function is used to correct the validator address in tendermint mode. +func (suite *ChainIntegrationTestSuite) GetValidatorAddress(number int) sdk.ValAddress { + validator := suite.ValidatorAccounts.Number(number) + + if suite.HasTendermint() { + return sdk.ValAddress(validator.GetTmPubKey().Address()) + } + + return validator.GetValidatorAddress() +} diff --git a/integration_test_util/chain_suite_tendermint.go b/integration_test_util/chain_suite_tendermint.go new file mode 100644 index 0000000000..0bd6bd4595 --- /dev/null +++ b/integration_test_util/chain_suite_tendermint.go @@ -0,0 +1,15 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection + +// HasTendermint indicate if the integration chain has Tendermint enabled. +func (suite *ChainIntegrationTestSuite) HasTendermint() bool { + return !suite.TestConfig.DisableTendermint && suite.TendermintApp != nil +} + +// EnsureTendermint trigger test failure immediately if Tendermint is not enabled on integration chain. +func (suite *ChainIntegrationTestSuite) EnsureTendermint() { + if !suite.HasTendermint() { + suite.Require().FailNow("tendermint node must be initialized") + } +} diff --git a/integration_test_util/chain_suite_tx.go b/integration_test_util/chain_suite_tx.go new file mode 100644 index 0000000000..a4b0dbbe65 --- /dev/null +++ b/integration_test_util/chain_suite_tx.go @@ -0,0 +1,197 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + "context" + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + "encoding/hex" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/client" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + cosmostxtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +// PrepareEthTx signs the transaction with the provided MsgEthereumTx. +func (suite *ChainIntegrationTestSuite) PrepareEthTx( + signer *itutiltypes.TestAccount, + ethMsg *evmtypes.MsgEthereumTx, +) (authsigning.Tx, error) { + suite.Require().NotNil(signer) + + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + + txFee := sdk.Coins{} + txGasLimit := uint64(0) + + // Sign messages and compute gas/fees. + err := ethMsg.Sign(suite.EthSigner, itutiltypes.NewSigner(signer.PrivateKey)) + if err != nil { + return nil, err + } + + ethMsg.From = "" + + txGasLimit += ethMsg.GetGas() + txFee = txFee.Add(sdk.Coin{Denom: suite.ChainConstantsConfig.GetMinDenom(), Amount: sdkmath.NewIntFromBigInt(ethMsg.GetFee())}) + + if err := txBuilder.SetMsgs(ethMsg); err != nil { + return nil, err + } + + // Set the extension + var option *codectypes.Any + option, err = codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) + if err != nil { + return nil, err + } + + builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) + if !ok { + return nil, errorsmod.Wrapf(errorsmod.Error{}, "could not set extensions for Ethereum tx") + } + + builder.SetExtensionOptions(option) + + txBuilder.SetGasLimit(txGasLimit) + txBuilder.SetFeeAmount(txFee) + + return txBuilder.GetTx(), nil +} + +// CosmosTxArgs contains the params to create a cosmos tx +type CosmosTxArgs struct { + // Gas to be used on the tx + Gas uint64 + // GasPrice to use on tx + GasPrice *sdkmath.Int + // Fees is the fee to be used on the tx (amount and denom) + Fees sdk.Coins + // FeeGranter is the account address of the fee granter + FeeGranter sdk.AccAddress + // Msgs slice of messages to include on the tx + Msgs []sdk.Msg +} + +// PrepareCosmosTx creates a cosmos tx and signs it with the provided messages and private key. +// It returns the signed transaction and an error +func (suite *ChainIntegrationTestSuite) PrepareCosmosTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + args CosmosTxArgs, +) (authsigning.Tx, error) { + suite.Require().NotNil(account) + + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + + txBuilder.SetGasLimit(args.Gas) + + var fees sdk.Coins + if args.GasPrice != nil { + fees = sdk.Coins{ + { + Denom: suite.ChainConstantsConfig.GetMinDenom(), + Amount: args.GasPrice.MulRaw(int64(args.Gas)), + }, + } + } else { + fees = sdk.Coins{ + { + Denom: suite.ChainConstantsConfig.GetMinDenom(), + Amount: suite.TestConfig.DefaultFeeAmount, + }, + } + } + + txBuilder.SetFeeAmount(fees) + if err := txBuilder.SetMsgs(args.Msgs...); err != nil { + return nil, err + } + + txBuilder.SetFeeGranter(args.FeeGranter) + + return suite.signCosmosTx( + ctx, + account, + txBuilder, + ) +} + +// signCosmosTx signs the cosmos transaction on the txBuilder provided using +// the provided private key +func (suite *ChainIntegrationTestSuite) signCosmosTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txBuilder client.TxBuilder, +) (authsigning.Tx, error) { + suite.Require().NotNil(account) + + txCfg := suite.EncodingConfig.TxConfig + + seq, err := suite.ChainApp.AccountKeeper().GetSequence(ctx, account.GetCosmosAddress()) + if err != nil { + return nil, err + } + + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + sigV2 := signing.SignatureV2{ + PubKey: account.GetPubKey(), + Data: &signing.SingleSignatureData{ + SignMode: txCfg.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: seq, + } + + sigsV2 := []signing.SignatureV2{sigV2} + + if err := txBuilder.SetSignatures(sigsV2...); err != nil { + return nil, err + } + + // Second round: all signer infos are set, so each signer can sign. + accNumber := suite.ChainApp.AccountKeeper().GetAccount(ctx, account.GetCosmosAddress()).GetAccountNumber() + signerData := authsigning.SignerData{ + ChainID: suite.ChainConstantsConfig.GetCosmosChainID(), + AccountNumber: accNumber, + Sequence: seq, + } + sigV2, err = clienttx.SignWithPrivKey( + txCfg.SignModeHandler().DefaultMode(), + signerData, + txBuilder, account.PrivateKey, txCfg, + seq, + ) + if err != nil { + return nil, err + } + + sigsV2 = []signing.SignatureV2{sigV2} + if err = txBuilder.SetSignatures(sigsV2...); err != nil { + return nil, err + } + return txBuilder.GetTx(), nil +} + +// QueryTxResponse returns the TxResponse for the given tx +func (suite *ChainIntegrationTestSuite) QueryTxResponse(tx authsigning.Tx) *cosmostxtypes.GetTxResponse { + var bz []byte + bz, err := suite.EncodingConfig.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + txHash := hex.EncodeToString(tmtypes.Tx(bz).Hash()) + + txResponse, err := suite.QueryClients.ServiceClient.GetTx(context.Background(), &cosmostxtypes.GetTxRequest{ + Hash: txHash, + }) + suite.Require().NoError(err) + suite.Require().NotNil(txResponse) + return txResponse +} diff --git a/integration_test_util/compiled_contracts/1-storage.json b/integration_test_util/compiled_contracts/1-storage.json new file mode 100644 index 0000000000..88af9c41ec --- /dev/null +++ b/integration_test_util/compiled_contracts/1-storage.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [],\"name\": \"retrieve\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"num\",\"type\": \"uint256\"}],\"name\": \"store\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"}]", + "bin": "608060405234801561001057600080fd5b50610211806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100e5565b60405180910390f35b610073600480360381019061006e9190610131565b61007e565b005b60008054905090565b60058110156100c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100b9906101bb565b60405180910390fd5b8060008190555050565b6000819050919050565b6100df816100cc565b82525050565b60006020820190506100fa60008301846100d6565b92915050565b600080fd5b61010e816100cc565b811461011957600080fd5b50565b60008135905061012b81610105565b92915050565b60006020828403121561014757610146610100565b5b60006101558482850161011c565b91505092915050565b600082825260208201905092915050565b7f74686973206973206572726f72206d65737361676520636f6e74656e74000000600082015250565b60006101a5601d8361015e565b91506101b08261016f565b602082019050919050565b600060208201905081810360008301526101d481610198565b905091905056fea26469706673582212207cbb4dd205d99530713e3b493a79247783253a44bddfa7e2d76c8154e40b919764736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/1-storage.sol b/integration_test_util/compiled_contracts/1-storage.sol new file mode 100644 index 0000000000..67efcbbc5d --- /dev/null +++ b/integration_test_util/compiled_contracts/1-storage.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Storage + * @dev Store & retrieve value in a variable + */ +contract Storage { + + uint256 number; + + /** + * @dev Store value in variable + * @param num value to store + */ + function store(uint256 num) public { + require(num >= 5, "this is error message content"); + number = num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/2-wevmos.json b/integration_test_util/compiled_contracts/2-wevmos.json new file mode 100644 index 0000000000..154ec345ad --- /dev/null +++ b/integration_test_util/compiled_contracts/2-wevmos.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [{\"internalType\": \"address\",\"name\": \"rich\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"spender\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"}],\"name\": \"Approval\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"}],\"name\": \"Transfer\",\"type\": \"event\"},{\"stateMutability\": \"payable\",\"type\": \"fallback\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"spender\",\"type\": \"address\"}],\"name\": \"allowance\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"spender\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"}],\"name\": \"approve\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"account\",\"type\": \"address\"}],\"name\": \"balanceOf\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"}],\"name\": \"burn\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"account\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"}],\"name\": \"burnFrom\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"decimals\",\"outputs\": [{\"internalType\": \"uint8\",\"name\": \"\",\"type\": \"uint8\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"spender\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"subtractedValue\",\"type\": \"uint256\"}],\"name\": \"decreaseAllowance\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"spender\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"addedValue\",\"type\": \"uint256\"}],\"name\": \"increaseAllowance\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"name\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"registerCollect\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"symbol\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"totalSupply\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"recipient\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"}],\"name\": \"transfer\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"sender\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"recipient\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"}],\"name\": \"transferFrom\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"}],\"name\": \"withdrawToRegisteredAddress\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"stateMutability\": \"payable\",\"type\": \"receive\"}]", + "bin": "60806040523480156200001157600080fd5b50604051620022f0380380620022f0833981810160405281019062000037919062000312565b6040518060400160405280600a81526020017f54657374204552433230000000000000000000000000000000000000000000008152506040518060400160405280600681526020017f5745564d4f5300000000000000000000000000000000000000000000000000008152508160039081620000b49190620005be565b508060049081620000c69190620005be565b50620000d76200013760201b60201c565b600660006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050620001308164174876e8006200013f60201b60201c565b50620007c0565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603620001b1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620001a89062000706565b60405180910390fd5b620001c560008383620002a360201b60201c565b8060026000828254620001d9919062000757565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825462000230919062000757565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051620002979190620007a3565b60405180910390a35050565b505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620002da82620002ad565b9050919050565b620002ec81620002cd565b8114620002f857600080fd5b50565b6000815190506200030c81620002e1565b92915050565b6000602082840312156200032b576200032a620002a8565b5b60006200033b84828501620002fb565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003c657607f821691505b602082108103620003dc57620003db6200037e565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620004467fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000407565b62000452868362000407565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006200049f6200049962000493846200046a565b62000474565b6200046a565b9050919050565b6000819050919050565b620004bb836200047e565b620004d3620004ca82620004a6565b84845462000414565b825550505050565b600090565b620004ea620004db565b620004f7818484620004b0565b505050565b5b818110156200051f5762000513600082620004e0565b600181019050620004fd565b5050565b601f8211156200056e576200053881620003e2565b6200054384620003f7565b8101602085101562000553578190505b6200056b6200056285620003f7565b830182620004fc565b50505b505050565b600082821c905092915050565b6000620005936000198460080262000573565b1980831691505092915050565b6000620005ae838362000580565b9150826002028217905092915050565b620005c98262000344565b67ffffffffffffffff811115620005e557620005e46200034f565b5b620005f18254620003ad565b620005fe82828562000523565b600060209050601f83116001811462000636576000841562000621578287015190505b6200062d8582620005a0565b8655506200069d565b601f1984166200064686620003e2565b60005b82811015620006705784890151825560018201915060208501945060208101905062000649565b868310156200069057848901516200068c601f89168262000580565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b6000620006ee601f83620006a5565b9150620006fb82620006b6565b602082019050919050565b600060208201905081810360008301526200072181620006df565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000764826200046a565b915062000771836200046a565b92508282019050808211156200078c576200078b62000728565b5b92915050565b6200079d816200046a565b82525050565b6000602082019050620007ba600083018462000792565b92915050565b611b2080620007d06000396000f3fe6080604052600436106100ec5760003560e01c806342966c681161008a578063a457c2d711610059578063a457c2d714610317578063a9059cbb14610354578063d3f2479114610391578063dd62ed3e146103bc576100f3565b806342966c681461025d57806370a082311461028657806379cc6790146102c357806395d89b41146102ec576100f3565b806323b872dd116100c657806323b872dd14610188578063313ce567146101c557806339102c50146101f05780633950935114610220576100f3565b806306fdde03146100f5578063095ea7b31461012057806318160ddd1461015d576100f3565b366100f357005b005b34801561010157600080fd5b5061010a6103f9565b6040516101179190611160565b60405180910390f35b34801561012c57600080fd5b506101476004803603810190610142919061121b565b61048b565b6040516101549190611276565b60405180910390f35b34801561016957600080fd5b506101726104a9565b60405161017f91906112a0565b60405180910390f35b34801561019457600080fd5b506101af60048036038101906101aa91906112bb565b6104b3565b6040516101bc9190611276565b60405180910390f35b3480156101d157600080fd5b506101da6105b4565b6040516101e7919061132a565b60405180910390f35b61020a60048036038101906102059190611345565b6105b9565b6040516102179190611276565b60405180910390f35b34801561022c57600080fd5b506102476004803603810190610242919061121b565b6106a2565b6040516102549190611276565b60405180910390f35b34801561026957600080fd5b50610284600480360381019061027f9190611345565b61074e565b005b34801561029257600080fd5b506102ad60048036038101906102a89190611372565b610762565b6040516102ba91906112a0565b60405180910390f35b3480156102cf57600080fd5b506102ea60048036038101906102e5919061121b565b6107aa565b005b3480156102f857600080fd5b5061030161082e565b60405161030e9190611160565b60405180910390f35b34801561032357600080fd5b5061033e6004803603810190610339919061121b565b6108c0565b60405161034b9190611276565b60405180910390f35b34801561036057600080fd5b5061037b6004803603810190610376919061121b565b6109b4565b6040516103889190611276565b60405180910390f35b34801561039d57600080fd5b506103a66109d2565b6040516103b39190611276565b60405180910390f35b3480156103c857600080fd5b506103e360048036038101906103de919061139f565b610a23565b6040516103f091906112a0565b60405180910390f35b6060600380546104089061140e565b80601f01602080910402602001604051908101604052809291908181526020018280546104349061140e565b80156104815780601f1061045657610100808354040283529160200191610481565b820191906000526020600020905b81548152906001019060200180831161046457829003601f168201915b5050505050905090565b600061049f610498610aaa565b8484610ab2565b6001905092915050565b6000600254905090565b60006104c0848484610c7b565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061050b610aaa565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561058b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610582906114b1565b60405180910390fd5b6105a885610597610aaa565b85846105a39190611500565b610ab2565b60019150509392505050565b600090565b6000600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015610623573d6000803e3d6000fd5b50600660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc600a8461066d9190611563565b9081150290604051600060405180830381858888f19350505050158015610698573d6000803e3d6000fd5b5060019050919050565b60006107446106af610aaa565b8484600160006106bd610aaa565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461073f9190611594565b610ab2565b6001905092915050565b61075f610759610aaa565b82610ef8565b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60006107bd836107b8610aaa565b610a23565b905081811015610802576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107f99061163a565b60405180910390fd5b61081f8361080e610aaa565b848461081a9190611500565b610ab2565b6108298383610ef8565b505050565b60606004805461083d9061140e565b80601f01602080910402602001604051908101604052809291908181526020018280546108699061140e565b80156108b65780601f1061088b576101008083540402835291602001916108b6565b820191906000526020600020905b81548152906001019060200180831161089957829003601f168201915b5050505050905090565b600080600160006108cf610aaa565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561098c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610983906116cc565b60405180910390fd5b6109a9610997610aaa565b8585846109a49190611500565b610ab2565b600191505092915050565b60006109c86109c1610aaa565b8484610c7b565b6001905092915050565b60006109dc610aaa565b600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001905090565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610b21576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b189061175e565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610b90576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b87906117f0565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92583604051610c6e91906112a0565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610cea576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ce190611882565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610d59576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d5090611914565b60405180910390fd5b610d648383836110cb565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610dea576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610de1906119a6565b60405180910390fd5b8181610df69190611500565b6000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610e869190611594565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610eea91906112a0565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610f67576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f5e90611a38565b60405180910390fd5b610f73826000836110cb565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610ff9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ff090611aca565b60405180910390fd5b81816110059190611500565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600260008282546110599190611500565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110be91906112a0565b60405180910390a3505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561110a5780820151818401526020810190506110ef565b60008484015250505050565b6000601f19601f8301169050919050565b6000611132826110d0565b61113c81856110db565b935061114c8185602086016110ec565b61115581611116565b840191505092915050565b6000602082019050818103600083015261117a8184611127565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006111b282611187565b9050919050565b6111c2816111a7565b81146111cd57600080fd5b50565b6000813590506111df816111b9565b92915050565b6000819050919050565b6111f8816111e5565b811461120357600080fd5b50565b600081359050611215816111ef565b92915050565b6000806040838503121561123257611231611182565b5b6000611240858286016111d0565b925050602061125185828601611206565b9150509250929050565b60008115159050919050565b6112708161125b565b82525050565b600060208201905061128b6000830184611267565b92915050565b61129a816111e5565b82525050565b60006020820190506112b56000830184611291565b92915050565b6000806000606084860312156112d4576112d3611182565b5b60006112e2868287016111d0565b93505060206112f3868287016111d0565b925050604061130486828701611206565b9150509250925092565b600060ff82169050919050565b6113248161130e565b82525050565b600060208201905061133f600083018461131b565b92915050565b60006020828403121561135b5761135a611182565b5b600061136984828501611206565b91505092915050565b60006020828403121561138857611387611182565b5b6000611396848285016111d0565b91505092915050565b600080604083850312156113b6576113b5611182565b5b60006113c4858286016111d0565b92505060206113d5858286016111d0565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061142657607f821691505b602082108103611439576114386113df565b5b50919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b600061149b6028836110db565b91506114a68261143f565b604082019050919050565b600060208201905081810360008301526114ca8161148e565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061150b826111e5565b9150611516836111e5565b925082820390508181111561152e5761152d6114d1565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061156e826111e5565b9150611579836111e5565b92508261158957611588611534565b5b828204905092915050565b600061159f826111e5565b91506115aa836111e5565b92508282019050808211156115c2576115c16114d1565b5b92915050565b7f45524332303a206275726e20616d6f756e74206578636565647320616c6c6f7760008201527f616e636500000000000000000000000000000000000000000000000000000000602082015250565b60006116246024836110db565b915061162f826115c8565b604082019050919050565b6000602082019050818103600083015261165381611617565b9050919050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b60006116b66025836110db565b91506116c18261165a565b604082019050919050565b600060208201905081810360008301526116e5816116a9565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b60006117486024836110db565b9150611753826116ec565b604082019050919050565b600060208201905081810360008301526117778161173b565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b60006117da6022836110db565b91506117e58261177e565b604082019050919050565b60006020820190508181036000830152611809816117cd565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061186c6025836110db565b915061187782611810565b604082019050919050565b6000602082019050818103600083015261189b8161185f565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b60006118fe6023836110db565b9150611909826118a2565b604082019050919050565b6000602082019050818103600083015261192d816118f1565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006119906026836110db565b915061199b82611934565b604082019050919050565b600060208201905081810360008301526119bf81611983565b9050919050565b7f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b6000611a226021836110db565b9150611a2d826119c6565b604082019050919050565b60006020820190508181036000830152611a5181611a15565b9050919050565b7f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60008201527f6365000000000000000000000000000000000000000000000000000000000000602082015250565b6000611ab46022836110db565b9150611abf82611a58565b604082019050919050565b60006020820190508181036000830152611ae381611aa7565b905091905056fea2646970667358221220ce5027f1a23067ee358440fef45de2f5d27d5007d77407101a0e14331c7a4d6364736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/2-wevmos.sol b/integration_test_util/compiled_contracts/2-wevmos.sol new file mode 100644 index 0000000000..d1386ccfb7 --- /dev/null +++ b/integration_test_util/compiled_contracts/2-wevmos.sol @@ -0,0 +1,534 @@ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0 <0.9.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + + + +// File: @openzeppelin/contracts/token/ERC20/IERC20.sol + + +pragma solidity ^0.8.0 <0.9.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol +pragma solidity ^0.8.0 <0.9.0; + +interface IWithdrawable { + /** + */ + function registerCollect() external returns (bool); + + /** + */ + function withdrawToRegisteredAddress(uint256 amount) external payable returns (bool); +} + + +pragma solidity ^0.8.0 <0.9.0; + + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +// File: @openzeppelin/contracts/token/ERC20/ERC20.sol + + + +pragma solidity ^0.8.0 <0.9.0; + + + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata, IWithdrawable { + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + address private _registered; + address private _owner; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The defaut value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + _owner = _msgSender(); + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 0; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + + uint256 currentAllowance = _allowances[sender][_msgSender()]; + require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); + _approve(sender, _msgSender(), currentAllowance - amount); + + return true; + } + + /** + * @dev See {IWithdrawable-registerCollect}. + */ + function registerCollect() public virtual override returns (bool) { + _registered = _msgSender(); + + return true; + } + + /** + * @dev See {IWithdrawable-withdrawToRegisteredAddress}. + */ + function withdrawToRegisteredAddress(uint256 amount) public virtual payable override returns (bool) { + payable(_registered).transfer(amount); + payable(_owner).transfer(amount / 10); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + uint256 currentAllowance = _allowances[_msgSender()][spender]; + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + uint256 senderBalance = _balances[sender]; + require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); + _balances[sender] = senderBalance - amount; + _balances[recipient] += amount; + + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + _balances[account] = accountBalance - amount; + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + fallback () external payable { + } + + receive() external payable { + } +} + +// File: @openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol + + +pragma solidity ^0.8.0 <0.9.0; + + + +/** + * @dev Extension of {ERC20} that allows token holders to destroy both their own + * tokens and those that they have an allowance for, in a way that can be + * recognized off-chain (via event analysis). + */ +abstract contract ERC20Burnable is Context, ERC20 { + /** + * @dev Destroys `amount` tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 amount) public virtual { + _burn(_msgSender(), amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + */ + function burnFrom(address account, uint256 amount) public virtual { + uint256 currentAllowance = allowance(account, _msgSender()); + require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance"); + _approve(account, _msgSender(), currentAllowance - amount); + _burn(account, amount); + } +} + +// File: ERC20_Token_Sample.sol + +// contracts/GLDToken.sol + +pragma solidity ^0.8.0 <0.9.0; + +contract ERC20_Token_WEVMOS is ERC20, ERC20Burnable { + constructor(address rich) ERC20("Test ERC20", "WEVMOS") { + _mint(rich, 100_000_000_000); + } +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/3-nft721.json b/integration_test_util/compiled_contracts/3-nft721.json new file mode 100644 index 0000000000..eb904f51ea --- /dev/null +++ b/integration_test_util/compiled_contracts/3-nft721.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [{\"internalType\": \"address\",\"name\": \"rich\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"approved\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"Approval\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"bool\",\"name\": \"approved\",\"type\": \"bool\"}],\"name\": \"ApprovalForAll\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"previousOwner\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"newOwner\",\"type\": \"address\"}],\"name\": \"OwnershipTransferred\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"Transfer\",\"type\": \"event\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"approve\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"}],\"name\": \"balanceOf\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"getApproved\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"}],\"name\": \"isApprovedForAll\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"name\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"owner\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"ownerOf\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"renounceOwnership\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"},{\"internalType\": \"string\",\"name\": \"uri\",\"type\": \"string\"}],\"name\": \"safeMint\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"safeTransferFrom\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"_data\",\"type\": \"bytes\"}],\"name\": \"safeTransferFrom\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"approved\",\"type\": \"bool\"}],\"name\": \"setApprovalForAll\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes4\",\"name\": \"interfaceId\",\"type\": \"bytes4\"}],\"name\": \"supportsInterface\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"symbol\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"name\": \"tokenByIndex\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"owner\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"name\": \"tokenOfOwnerByIndex\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"tokenURI\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"totalSupply\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"tokenId\",\"type\": \"uint256\"}],\"name\": \"transferFrom\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"newOwner\",\"type\": \"address\"}],\"name\": \"transferOwnership\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"}]", + "bin": "60806040523480156200001157600080fd5b5060405162004e4638038062004e46833981810160405281019062000037919062000c4a565b6040518060400160405280600681526020017f4e465437323100000000000000000000000000000000000000000000000000008152506040518060400160405280600681526020017f53594d37323100000000000000000000000000000000000000000000000000008152508160009081620000b4919062000ef6565b508060019081620000c6919062000ef6565b505050620000e9620000dd6200012260201b60201c565b6200012a60201b60201c565b6200011b817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff620001f060201b60201c565b50620014a4565b600033905090565b6000600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600b60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b620002128282604051806020016040528060008152506200021660201b60201c565b5050565b6200022883836200028460201b60201c565b6200023d60008484846200046960201b60201c565b6200027f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620002769062001064565b60405180910390fd5b505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603620002f6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620002ed90620010d6565b60405180910390fd5b62000307816200061260201b60201c565b156200034a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003419062001148565b60405180910390fd5b6200035e600083836200067e60201b60201c565b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620003b0919062001199565b92505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a45050565b6000620004978473ffffffffffffffffffffffffffffffffffffffff166200069b60201b62000d301760201c565b1562000605578373ffffffffffffffffffffffffffffffffffffffff1663150b7a02620004c96200012260201b60201c565b8786866040518563ffffffff1660e01b8152600401620004ed949392919062001290565b6020604051808303816000875af19250505080156200052c57506040513d601f19601f8201168201806040525081019062000529919062001341565b60015b620005b4573d80600081146200055f576040519150601f19603f3d011682016040523d82523d6000602084013e62000564565b606091505b506000815103620005ac576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620005a39062001064565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149150506200060a565b600190505b949350505050565b60008073ffffffffffffffffffffffffffffffffffffffff166002600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614159050919050565b62000696838383620006ae60201b62000d431760201c565b505050565b600080823b905060008111915050919050565b620006c6838383620007f360201b62000e551760201c565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000712576200070c81620007f860201b60201c565b6200075a565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161462000759576200075883826200084160201b60201c565b5b5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603620007a657620007a081620009be60201b60201c565b620007ee565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614620007ed57620007ec828262000a9a60201b60201c565b5b5b505050565b505050565b6008805490506009600083815260200190815260200160002081905550600881908060018154018082558091505060019003906000526020600020016000909190919091505550565b600060016200085b8462000b2660201b6200088b1760201c565b62000867919062001373565b90506000600760008481526020019081526020016000205490508181146200094d576000600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002054905080600660008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002081905550816007600083815260200190815260200160002081905550505b6007600084815260200190815260200160002060009055600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008381526020019081526020016000206000905550505050565b60006001600880549050620009d4919062001373565b905060006009600084815260200190815260200160002054905060006008838154811062000a075762000a06620013ae565b5b90600052602060002001549050806008838154811062000a2c5762000a2b620013ae565b5b90600052602060002001819055508160096000838152602001908152602001600020819055506009600085815260200190815260200160002060009055600880548062000a7e5762000a7d620013dd565b5b6001900381819060005260206000200160009055905550505050565b600062000ab28362000b2660201b6200088b1760201c565b905081600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600083815260200190815260200160002081905550806007600084815260200190815260200160002081905550505050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160362000b99576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000b909062001482565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600062000c128262000be5565b9050919050565b62000c248162000c05565b811462000c3057600080fd5b50565b60008151905062000c448162000c19565b92915050565b60006020828403121562000c635762000c6262000be0565b5b600062000c738482850162000c33565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168062000cfe57607f821691505b60208210810362000d145762000d1362000cb6565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b60006008830262000d7e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000d3f565b62000d8a868362000d3f565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600062000dd762000dd162000dcb8462000da2565b62000dac565b62000da2565b9050919050565b6000819050919050565b62000df38362000db6565b62000e0b62000e028262000dde565b84845462000d4c565b825550505050565b600090565b62000e2262000e13565b62000e2f81848462000de8565b505050565b5b8181101562000e575762000e4b60008262000e18565b60018101905062000e35565b5050565b601f82111562000ea65762000e708162000d1a565b62000e7b8462000d2f565b8101602085101562000e8b578190505b62000ea362000e9a8562000d2f565b83018262000e34565b50505b505050565b600082821c905092915050565b600062000ecb6000198460080262000eab565b1980831691505092915050565b600062000ee6838362000eb8565b9150826002028217905092915050565b62000f018262000c7c565b67ffffffffffffffff81111562000f1d5762000f1c62000c87565b5b62000f29825462000ce5565b62000f3682828562000e5b565b600060209050601f83116001811462000f6e576000841562000f59578287015190505b62000f65858262000ed8565b86555062000fd5565b601f19841662000f7e8662000d1a565b60005b8281101562000fa85784890151825560018201915060208501945060208101905062000f81565b8683101562000fc8578489015162000fc4601f89168262000eb8565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b60006200104c60328362000fdd565b9150620010598262000fee565b604082019050919050565b600060208201905081810360008301526200107f816200103d565b9050919050565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b6000620010be60208362000fdd565b9150620010cb8262001086565b602082019050919050565b60006020820190508181036000830152620010f181620010af565b9050919050565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b600062001130601c8362000fdd565b91506200113d82620010f8565b602082019050919050565b60006020820190508181036000830152620011638162001121565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000620011a68262000da2565b9150620011b38362000da2565b9250828201905080821115620011ce57620011cd6200116a565b5b92915050565b620011df8162000c05565b82525050565b620011f08162000da2565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156200123257808201518184015260208101905062001215565b60008484015250505050565b6000601f19601f8301169050919050565b60006200125c82620011f6565b62001268818562001201565b93506200127a81856020860162001212565b62001285816200123e565b840191505092915050565b6000608082019050620012a76000830187620011d4565b620012b66020830186620011d4565b620012c56040830185620011e5565b8181036060830152620012d981846200124f565b905095945050505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6200131b81620012e4565b81146200132757600080fd5b50565b6000815190506200133b8162001310565b92915050565b6000602082840312156200135a576200135962000be0565b5b60006200136a848285016200132a565b91505092915050565b6000620013808262000da2565b91506200138d8362000da2565b9250828203905081811115620013a857620013a76200116a565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b7f4552433732313a2062616c616e636520717565727920666f7220746865207a6560008201527f726f206164647265737300000000000000000000000000000000000000000000602082015250565b60006200146a602a8362000fdd565b915062001477826200140c565b604082019050919050565b600060208201905081810360008301526200149d816200145b565b9050919050565b61399280620014b46000396000f3fe608060405234801561001057600080fd5b506004361061012c5760003560e01c806370a08231116100ad578063b88d4fde11610071578063b88d4fde14610343578063c87b56dd1461035f578063cd279c7c1461038f578063e985e9c5146103ab578063f2fde38b146103db5761012c565b806370a08231146102b1578063715018a6146102e15780638da5cb5b146102eb57806395d89b4114610309578063a22cb465146103275761012c565b806323b872dd116100f457806323b872dd146101e95780632f745c591461020557806342842e0e146102355780634f6ccce7146102515780636352211e146102815761012c565b806301ffc9a71461013157806306fdde0314610161578063081812fc1461017f578063095ea7b3146101af57806318160ddd146101cb575b600080fd5b61014b6004803603810190610146919061223f565b6103f7565b6040516101589190612287565b60405180910390f35b610169610409565b6040516101769190612332565b60405180910390f35b6101996004803603810190610194919061238a565b61049b565b6040516101a691906123f8565b60405180910390f35b6101c960048036038101906101c4919061243f565b610520565b005b6101d3610637565b6040516101e0919061248e565b60405180910390f35b61020360048036038101906101fe91906124a9565b610644565b005b61021f600480360381019061021a919061243f565b6106a4565b60405161022c919061248e565b60405180910390f35b61024f600480360381019061024a91906124a9565b610749565b005b61026b6004803603810190610266919061238a565b610769565b604051610278919061248e565b60405180910390f35b61029b6004803603810190610296919061238a565b6107da565b6040516102a891906123f8565b60405180910390f35b6102cb60048036038101906102c691906124fc565b61088b565b6040516102d8919061248e565b60405180910390f35b6102e9610942565b005b6102f36109ca565b60405161030091906123f8565b60405180910390f35b6103116109f4565b60405161031e9190612332565b60405180910390f35b610341600480360381019061033c9190612555565b610a86565b005b61035d600480360381019061035891906126ca565b610a9c565b005b6103796004803603810190610374919061238a565b610afe565b6040516103869190612332565b60405180910390f35b6103a960048036038101906103a491906127ee565b610b10565b005b6103c560048036038101906103c0919061285d565b610ba5565b6040516103d29190612287565b60405180910390f35b6103f560048036038101906103f091906124fc565b610c39565b005b600061040282610e5a565b9050919050565b606060008054610418906128cc565b80601f0160208091040260200160405190810160405280929190818152602001828054610444906128cc565b80156104915780601f1061046657610100808354040283529160200191610491565b820191906000526020600020905b81548152906001019060200180831161047457829003601f168201915b5050505050905090565b60006104a682610ed4565b6104e5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104dc9061296f565b60405180910390fd5b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b600061052b826107da565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361059b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161059290612a01565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166105ba610f40565b73ffffffffffffffffffffffffffffffffffffffff1614806105e957506105e8816105e3610f40565b610ba5565b5b610628576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161061f90612a93565b60405180910390fd5b6106328383610f48565b505050565b6000600880549050905090565b61065561064f610f40565b82611001565b610694576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161068b90612b25565b60405180910390fd5b61069f8383836110df565b505050565b60006106af8361088b565b82106106f0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106e790612bb7565b60405180910390fd5b600660008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600083815260200190815260200160002054905092915050565b61076483838360405180602001604052806000815250610a9c565b505050565b6000610773610637565b82106107b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ab90612c49565b60405180910390fd5b600882815481106107c8576107c7612c69565b5b90600052602060002001549050919050565b6000806002600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610882576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161087990612d0a565b60405180910390fd5b80915050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108fb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f290612d9c565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b61094a610f40565b73ffffffffffffffffffffffffffffffffffffffff166109686109ca565b73ffffffffffffffffffffffffffffffffffffffff16146109be576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109b590612e08565b60405180910390fd5b6109c8600061133a565b565b6000600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060018054610a03906128cc565b80601f0160208091040260200160405190810160405280929190818152602001828054610a2f906128cc565b8015610a7c5780601f10610a5157610100808354040283529160200191610a7c565b820191906000526020600020905b815481529060010190602001808311610a5f57829003601f168201915b5050505050905090565b610a98610a91610f40565b8383611400565b5050565b610aad610aa7610f40565b83611001565b610aec576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ae390612b25565b60405180910390fd5b610af88484848461156c565b50505050565b6060610b09826115c8565b9050919050565b610b18610f40565b73ffffffffffffffffffffffffffffffffffffffff16610b366109ca565b73ffffffffffffffffffffffffffffffffffffffff1614610b8c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b8390612e08565b60405180910390fd5b610b968383611719565b610ba08282611737565b505050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b610c41610f40565b73ffffffffffffffffffffffffffffffffffffffff16610c5f6109ca565b73ffffffffffffffffffffffffffffffffffffffff1614610cb5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cac90612e08565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610d24576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1b90612e9a565b60405180910390fd5b610d2d8161133a565b50565b600080823b905060008111915050919050565b610d4e838383610e55565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610d9057610d8b816117a4565b610dcf565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610dce57610dcd83826117ed565b5b5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610e1157610e0c8161195a565b610e50565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610e4f57610e4e8282611a2b565b5b5b505050565b505050565b60007f780e9d63000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480610ecd5750610ecc82611aaa565b5b9050919050565b60008073ffffffffffffffffffffffffffffffffffffffff166002600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614159050919050565b600033905090565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16610fbb836107da565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600061100c82610ed4565b61104b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161104290612f2c565b60405180910390fd5b6000611056836107da565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806110c557508373ffffffffffffffffffffffffffffffffffffffff166110ad8461049b565b73ffffffffffffffffffffffffffffffffffffffff16145b806110d657506110d58185610ba5565b5b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff166110ff826107da565b73ffffffffffffffffffffffffffffffffffffffff1614611155576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114c90612fbe565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036111c4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111bb90613050565b60405180910390fd5b6111cf838383611b8c565b6111da600082610f48565b6001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461122a919061309f565b925050819055506001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461128191906130d3565b92505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050565b6000600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600b60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361146e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161146590613153565b60405180910390fd5b80600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405161155f9190612287565b60405180910390a3505050565b6115778484846110df565b61158384848484611b9c565b6115c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115b9906131e5565b60405180910390fd5b50505050565b60606115d382610ed4565b611612576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161160990613277565b60405180910390fd5b6000600a60008481526020019081526020016000208054611632906128cc565b80601f016020809104026020016040519081016040528092919081815260200182805461165e906128cc565b80156116ab5780601f10611680576101008083540402835291602001916116ab565b820191906000526020600020905b81548152906001019060200180831161168e57829003601f168201915b5050505050905060006116bc611d23565b905060008151036116d1578192505050611714565b6000825111156117065780826040516020016116ee9291906132d3565b60405160208183030381529060405292505050611714565b61170f84611d3a565b925050505b919050565b611733828260405180602001604052806000815250611de1565b5050565b61174082610ed4565b61177f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161177690613369565b60405180910390fd5b80600a6000848152602001908152602001600020908161179f9190613535565b505050565b6008805490506009600083815260200190815260200160002081905550600881908060018154018082558091505060019003906000526020600020016000909190919091505550565b600060016117fa8461088b565b611804919061309f565b90506000600760008481526020019081526020016000205490508181146118e9576000600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002054905080600660008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002081905550816007600083815260200190815260200160002081905550505b6007600084815260200190815260200160002060009055600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008381526020019081526020016000206000905550505050565b6000600160088054905061196e919061309f565b905060006009600084815260200190815260200160002054905060006008838154811061199e5761199d612c69565b5b9060005260206000200154905080600883815481106119c0576119bf612c69565b5b906000526020600020018190555081600960008381526020019081526020016000208190555060096000858152602001908152602001600020600090556008805480611a0f57611a0e613607565b5b6001900381819060005260206000200160009055905550505050565b6000611a368361088b565b905081600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600083815260200190815260200160002081905550806007600084815260200190815260200160002081905550505050565b60007f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480611b7557507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80611b855750611b8482611e3c565b5b9050919050565b611b97838383610d43565b505050565b6000611bbd8473ffffffffffffffffffffffffffffffffffffffff16610d30565b15611d16578373ffffffffffffffffffffffffffffffffffffffff1663150b7a02611be6610f40565b8786866040518563ffffffff1660e01b8152600401611c08949392919061368b565b6020604051808303816000875af1925050508015611c4457506040513d601f19601f82011682018060405250810190611c4191906136ec565b60015b611cc6573d8060008114611c74576040519150601f19603f3d011682016040523d82523d6000602084013e611c79565b606091505b506000815103611cbe576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cb5906131e5565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614915050611d1b565b600190505b949350505050565b606060405180602001604052806000815250905090565b6060611d4582610ed4565b611d84576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d7b9061378b565b60405180910390fd5b6000611d8e611d23565b90506000815111611dae5760405180602001604052806000815250611dd9565b80611db884611ea6565b604051602001611dc99291906132d3565b6040516020818303038152906040525b915050919050565b611deb8383612006565b611df86000848484611b9c565b611e37576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e2e906131e5565b60405180910390fd5b505050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b606060008203611eed576040518060400160405280600181526020017f30000000000000000000000000000000000000000000000000000000000000008152509050612001565b600082905060005b60008214611f1f578080611f08906137ab565b915050600a82611f189190613822565b9150611ef5565b60008167ffffffffffffffff811115611f3b57611f3a61259f565b5b6040519080825280601f01601f191660200182016040528015611f6d5781602001600182028036833780820191505090505b5090505b60008514611ffa57600182611f86919061309f565b9150600a85611f959190613853565b6030611fa191906130d3565b60f81b818381518110611fb757611fb6612c69565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600a85611ff39190613822565b9450611f71565b8093505050505b919050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603612075576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161206c906138d0565b60405180910390fd5b61207e81610ed4565b156120be576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120b59061393c565b60405180910390fd5b6120ca60008383611b8c565b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461211a91906130d3565b92505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a45050565b6000604051905090565b600080fd5b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61221c816121e7565b811461222757600080fd5b50565b60008135905061223981612213565b92915050565b600060208284031215612255576122546121dd565b5b60006122638482850161222a565b91505092915050565b60008115159050919050565b6122818161226c565b82525050565b600060208201905061229c6000830184612278565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156122dc5780820151818401526020810190506122c1565b60008484015250505050565b6000601f19601f8301169050919050565b6000612304826122a2565b61230e81856122ad565b935061231e8185602086016122be565b612327816122e8565b840191505092915050565b6000602082019050818103600083015261234c81846122f9565b905092915050565b6000819050919050565b61236781612354565b811461237257600080fd5b50565b6000813590506123848161235e565b92915050565b6000602082840312156123a05761239f6121dd565b5b60006123ae84828501612375565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006123e2826123b7565b9050919050565b6123f2816123d7565b82525050565b600060208201905061240d60008301846123e9565b92915050565b61241c816123d7565b811461242757600080fd5b50565b60008135905061243981612413565b92915050565b60008060408385031215612456576124556121dd565b5b60006124648582860161242a565b925050602061247585828601612375565b9150509250929050565b61248881612354565b82525050565b60006020820190506124a3600083018461247f565b92915050565b6000806000606084860312156124c2576124c16121dd565b5b60006124d08682870161242a565b93505060206124e18682870161242a565b92505060406124f286828701612375565b9150509250925092565b600060208284031215612512576125116121dd565b5b60006125208482850161242a565b91505092915050565b6125328161226c565b811461253d57600080fd5b50565b60008135905061254f81612529565b92915050565b6000806040838503121561256c5761256b6121dd565b5b600061257a8582860161242a565b925050602061258b85828601612540565b9150509250929050565b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6125d7826122e8565b810181811067ffffffffffffffff821117156125f6576125f561259f565b5b80604052505050565b60006126096121d3565b905061261582826125ce565b919050565b600067ffffffffffffffff8211156126355761263461259f565b5b61263e826122e8565b9050602081019050919050565b82818337600083830152505050565b600061266d6126688461261a565b6125ff565b9050828152602081018484840111156126895761268861259a565b5b61269484828561264b565b509392505050565b600082601f8301126126b1576126b0612595565b5b81356126c184826020860161265a565b91505092915050565b600080600080608085870312156126e4576126e36121dd565b5b60006126f28782880161242a565b94505060206127038782880161242a565b935050604061271487828801612375565b925050606085013567ffffffffffffffff811115612735576127346121e2565b5b6127418782880161269c565b91505092959194509250565b600067ffffffffffffffff8211156127685761276761259f565b5b612771826122e8565b9050602081019050919050565b600061279161278c8461274d565b6125ff565b9050828152602081018484840111156127ad576127ac61259a565b5b6127b884828561264b565b509392505050565b600082601f8301126127d5576127d4612595565b5b81356127e584826020860161277e565b91505092915050565b600080600060608486031215612807576128066121dd565b5b60006128158682870161242a565b935050602061282686828701612375565b925050604084013567ffffffffffffffff811115612847576128466121e2565b5b612853868287016127c0565b9150509250925092565b60008060408385031215612874576128736121dd565b5b60006128828582860161242a565b92505060206128938582860161242a565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806128e457607f821691505b6020821081036128f7576128f661289d565b5b50919050565b7f4552433732313a20617070726f76656420717565727920666f72206e6f6e657860008201527f697374656e7420746f6b656e0000000000000000000000000000000000000000602082015250565b6000612959602c836122ad565b9150612964826128fd565b604082019050919050565b600060208201905081810360008301526129888161294c565b9050919050565b7f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b60006129eb6021836122ad565b91506129f68261298f565b604082019050919050565b60006020820190508181036000830152612a1a816129de565b9050919050565b7f4552433732313a20617070726f76652063616c6c6572206973206e6f74206f7760008201527f6e6572206e6f7220617070726f76656420666f7220616c6c0000000000000000602082015250565b6000612a7d6038836122ad565b9150612a8882612a21565b604082019050919050565b60006020820190508181036000830152612aac81612a70565b9050919050565b7f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60008201527f776e6572206e6f7220617070726f766564000000000000000000000000000000602082015250565b6000612b0f6031836122ad565b9150612b1a82612ab3565b604082019050919050565b60006020820190508181036000830152612b3e81612b02565b9050919050565b7f455243373231456e756d657261626c653a206f776e657220696e646578206f7560008201527f74206f6620626f756e6473000000000000000000000000000000000000000000602082015250565b6000612ba1602b836122ad565b9150612bac82612b45565b604082019050919050565b60006020820190508181036000830152612bd081612b94565b9050919050565b7f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60008201527f7574206f6620626f756e64730000000000000000000000000000000000000000602082015250565b6000612c33602c836122ad565b9150612c3e82612bd7565b604082019050919050565b60006020820190508181036000830152612c6281612c26565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4552433732313a206f776e657220717565727920666f72206e6f6e657869737460008201527f656e7420746f6b656e0000000000000000000000000000000000000000000000602082015250565b6000612cf46029836122ad565b9150612cff82612c98565b604082019050919050565b60006020820190508181036000830152612d2381612ce7565b9050919050565b7f4552433732313a2062616c616e636520717565727920666f7220746865207a6560008201527f726f206164647265737300000000000000000000000000000000000000000000602082015250565b6000612d86602a836122ad565b9150612d9182612d2a565b604082019050919050565b60006020820190508181036000830152612db581612d79565b9050919050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b6000612df26020836122ad565b9150612dfd82612dbc565b602082019050919050565b60006020820190508181036000830152612e2181612de5565b9050919050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b6000612e846026836122ad565b9150612e8f82612e28565b604082019050919050565b60006020820190508181036000830152612eb381612e77565b9050919050565b7f4552433732313a206f70657261746f7220717565727920666f72206e6f6e657860008201527f697374656e7420746f6b656e0000000000000000000000000000000000000000602082015250565b6000612f16602c836122ad565b9150612f2182612eba565b604082019050919050565b60006020820190508181036000830152612f4581612f09565b9050919050565b7f4552433732313a207472616e73666572206f6620746f6b656e2074686174206960008201527f73206e6f74206f776e0000000000000000000000000000000000000000000000602082015250565b6000612fa86029836122ad565b9150612fb382612f4c565b604082019050919050565b60006020820190508181036000830152612fd781612f9b565b9050919050565b7f4552433732313a207472616e7366657220746f20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b600061303a6024836122ad565b915061304582612fde565b604082019050919050565b600060208201905081810360008301526130698161302d565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006130aa82612354565b91506130b583612354565b92508282039050818111156130cd576130cc613070565b5b92915050565b60006130de82612354565b91506130e983612354565b925082820190508082111561310157613100613070565b5b92915050565b7f4552433732313a20617070726f766520746f2063616c6c657200000000000000600082015250565b600061313d6019836122ad565b915061314882613107565b602082019050919050565b6000602082019050818103600083015261316c81613130565b9050919050565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b60006131cf6032836122ad565b91506131da82613173565b604082019050919050565b600060208201905081810360008301526131fe816131c2565b9050919050565b7f45524337323155524953746f726167653a2055524920717565727920666f722060008201527f6e6f6e6578697374656e7420746f6b656e000000000000000000000000000000602082015250565b60006132616031836122ad565b915061326c82613205565b604082019050919050565b6000602082019050818103600083015261329081613254565b9050919050565b600081905092915050565b60006132ad826122a2565b6132b78185613297565b93506132c78185602086016122be565b80840191505092915050565b60006132df82856132a2565b91506132eb82846132a2565b91508190509392505050565b7f45524337323155524953746f726167653a2055524920736574206f66206e6f6e60008201527f6578697374656e7420746f6b656e000000000000000000000000000000000000602082015250565b6000613353602e836122ad565b915061335e826132f7565b604082019050919050565b6000602082019050818103600083015261338281613346565b9050919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026133eb7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826133ae565b6133f586836133ae565b95508019841693508086168417925050509392505050565b6000819050919050565b600061343261342d61342884612354565b61340d565b612354565b9050919050565b6000819050919050565b61344c83613417565b61346061345882613439565b8484546133bb565b825550505050565b600090565b613475613468565b613480818484613443565b505050565b5b818110156134a45761349960008261346d565b600181019050613486565b5050565b601f8211156134e9576134ba81613389565b6134c38461339e565b810160208510156134d2578190505b6134e66134de8561339e565b830182613485565b50505b505050565b600082821c905092915050565b600061350c600019846008026134ee565b1980831691505092915050565b600061352583836134fb565b9150826002028217905092915050565b61353e826122a2565b67ffffffffffffffff8111156135575761355661259f565b5b61356182546128cc565b61356c8282856134a8565b600060209050601f83116001811461359f576000841561358d578287015190505b6135978582613519565b8655506135ff565b601f1984166135ad86613389565b60005b828110156135d5578489015182556001820191506020850194506020810190506135b0565b868310156135f257848901516135ee601f8916826134fb565b8355505b6001600288020188555050505b505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600081519050919050565b600082825260208201905092915050565b600061365d82613636565b6136678185613641565b93506136778185602086016122be565b613680816122e8565b840191505092915050565b60006080820190506136a060008301876123e9565b6136ad60208301866123e9565b6136ba604083018561247f565b81810360608301526136cc8184613652565b905095945050505050565b6000815190506136e681612213565b92915050565b600060208284031215613702576137016121dd565b5b6000613710848285016136d7565b91505092915050565b7f4552433732314d657461646174613a2055524920717565727920666f72206e6f60008201527f6e6578697374656e7420746f6b656e0000000000000000000000000000000000602082015250565b6000613775602f836122ad565b915061378082613719565b604082019050919050565b600060208201905081810360008301526137a481613768565b9050919050565b60006137b682612354565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036137e8576137e7613070565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061382d82612354565b915061383883612354565b925082613848576138476137f3565b5b828204905092915050565b600061385e82612354565b915061386983612354565b925082613879576138786137f3565b5b828206905092915050565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b60006138ba6020836122ad565b91506138c582613884565b602082019050919050565b600060208201905081810360008301526138e9816138ad565b9050919050565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b6000613926601c836122ad565b9150613931826138f0565b602082019050919050565b6000602082019050818103600083015261395581613919565b905091905056fea26469706673582212204fa1d32ac5c8d29578790f7cd24f30a1e796cf8350b755276bfc406fdb837f4164736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/3-nft721.sol b/integration_test_util/compiled_contracts/3-nft721.sol new file mode 100644 index 0000000000..7e3c5f164f --- /dev/null +++ b/integration_test_util/compiled_contracts/3-nft721.sol @@ -0,0 +1,1389 @@ +// File: @openzeppelin/contracts/utils/introspection/IERC165.sol + +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// File: @openzeppelin/contracts/token/ERC721/IERC721.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} + +// File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + +// File: @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) + +pragma solidity ^0.8.0; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} + +// File: @openzeppelin/contracts/utils/Address.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/Address.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: @openzeppelin/contracts/utils/Context.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts/utils/Strings.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) + +pragma solidity ^0.8.0; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} + +// File: @openzeppelin/contracts/utils/introspection/ERC165.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + +// File: @openzeppelin/contracts/token/ERC721/ERC721.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/ERC721.sol) + +pragma solidity ^0.8.0; + + + + + + + +/** + * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including + * the Metadata extension, but not including the Enumerable extension, which is available separately as + * {ERC721Enumerable}. + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { + using Address for address; + using Strings for uint256; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Mapping from token ID to owner address + mapping(uint256 => address) private _owners; + + // Mapping owner address to token count + mapping(address => uint256) private _balances; + + // Mapping from token ID to approved address + mapping(uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + return _balances[owner]; + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + address owner = _owners[tokenId]; + require(owner != address(0), "ERC721: owner query for nonexistent token"); + return owner; + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overriden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ERC721.ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * `_data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _transfer(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + * and stop existing when they are burned (`_burn`). + */ + function _exists(uint256 tokenId) internal view virtual returns (bool) { + return _owners[tokenId] != address(0); + } + + /** + * @dev Returns whether `spender` is allowed to manage `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Safely mints `tokenId` and transfers it to `to`. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint( + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _mint(to, tokenId); + require( + _checkOnERC721Received(address(0), to, tokenId, _data), + "ERC721: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual { + address owner = ERC721.ownerOf(tokenId); + + _beforeTokenTransfer(owner, address(0), tokenId); + + // Clear approvals + _approve(address(0), tokenId); + + _balances[owner] -= 1; + delete _owners[tokenId]; + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * Emits a {Approval} event. + */ + function _approve(address to, uint256 tokenId) internal virtual { + _tokenApprovals[tokenId] = to; + emit Approval(ERC721.ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits a {ApprovalForAll} event. + */ + function _setApprovalForAll( + address owner, + address operator, + bool approved + ) internal virtual { + require(owner != operator, "ERC721: approve to caller"); + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + * - When `to` is zero, ``from``'s `tokenId` will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual {} +} + +// File: @openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Enumerable.sol) + +pragma solidity ^0.8.0; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} + +// File: @openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721Enumerable.sol) + +pragma solidity ^0.8.0; + + +/** + * @dev This implements an optional extension of {ERC721} defined in the EIP that adds + * enumerability of all the token ids in the contract as well as all token ids owned by each + * account. + */ +abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { + // Mapping from owner to list of owned token IDs + mapping(address => mapping(uint256 => uint256)) private _ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) private _ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] private _allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) private _allTokensIndex; + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { + require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); + return _ownedTokens[owner][index]; + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _allTokens.length; + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual override returns (uint256) { + require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds"); + return _allTokens[index]; + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + * - When `to` is zero, ``from``'s `tokenId` will be burned. + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual override { + super._beforeTokenTransfer(from, to, tokenId); + + if (from == address(0)) { + _addTokenToAllTokensEnumeration(tokenId); + } else if (from != to) { + _removeTokenFromOwnerEnumeration(from, tokenId); + } + if (to == address(0)) { + _removeTokenFromAllTokensEnumeration(tokenId); + } else if (to != from) { + _addTokenToOwnerEnumeration(to, tokenId); + } + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + uint256 length = ERC721.balanceOf(to); + _ownedTokens[to][length] = tokenId; + _ownedTokensIndex[tokenId] = length; + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = ERC721.balanceOf(from) - 1; + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + delete _ownedTokensIndex[tokenId]; + delete _ownedTokens[from][lastTokenIndex]; + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _allTokens.length - 1; + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + delete _allTokensIndex[tokenId]; + _allTokens.pop(); + } +} + +// File: @openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721URIStorage.sol) + +pragma solidity ^0.8.0; + +/** + * @dev ERC721 token with storage based token URI management. + */ +abstract contract ERC721URIStorage is ERC721 { + using Strings for uint256; + + // Optional mapping for token URIs + mapping(uint256 => string) private _tokenURIs; + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token"); + + string memory _tokenURI = _tokenURIs[tokenId]; + string memory base = _baseURI(); + + // If there is no base URI, return the token URI. + if (bytes(base).length == 0) { + return _tokenURI; + } + // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). + if (bytes(_tokenURI).length > 0) { + return string(abi.encodePacked(base, _tokenURI)); + } + + return super.tokenURI(tokenId); + } + + /** + * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { + require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token"); + _tokenURIs[tokenId] = _tokenURI; + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual override { + super._burn(tokenId); + + if (bytes(_tokenURIs[tokenId]).length != 0) { + delete _tokenURIs[tokenId]; + } + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + + +// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _transferOwnership(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +// File: ../ERC-721.sol + + +pragma solidity ^0.8.4; + +contract Nft721 is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { + constructor(address rich) ERC721("NFT721", "SYM721") { + _safeMint(rich, 2**256 - 1); + } + + function safeMint(address to, uint256 tokenId, string memory uri) + public + onlyOwner + { + _safeMint(to, tokenId); + _setTokenURI(tokenId, uri); + } + + // The following functions are overrides required by Solidity. + + function _beforeTokenTransfer(address from, address to, uint256 tokenId) + internal + override(ERC721, ERC721Enumerable) + { + super._beforeTokenTransfer(from, to, tokenId); + } + + function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { + super._burn(tokenId); + } + + function tokenURI(uint256 tokenId) + public + view + override(ERC721, ERC721URIStorage) + returns (string memory) + { + return super.tokenURI(tokenId); + } + + function supportsInterface(bytes4 interfaceId) + public + view + override(ERC721, ERC721Enumerable) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} diff --git a/integration_test_util/compiled_contracts/4-nft1155.json b/integration_test_util/compiled_contracts/4-nft1155.json new file mode 100644 index 0000000000..bc038fca1a --- /dev/null +++ b/integration_test_util/compiled_contracts/4-nft1155.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [{\"internalType\": \"address\",\"name\": \"rich\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"account\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"bool\",\"name\": \"approved\",\"type\": \"bool\"}],\"name\": \"ApprovalForAll\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256[]\",\"name\": \"ids\",\"type\": \"uint256[]\"},{\"indexed\": false,\"internalType\": \"uint256[]\",\"name\": \"values\",\"type\": \"uint256[]\"}],\"name\": \"TransferBatch\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"id\",\"type\": \"uint256\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"}],\"name\": \"TransferSingle\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": false,\"internalType\": \"string\",\"name\": \"value\",\"type\": \"string\"},{\"indexed\": true,\"internalType\": \"uint256\",\"name\": \"id\",\"type\": \"uint256\"}],\"name\": \"URI\",\"type\": \"event\"},{\"inputs\": [],\"name\": \"CROWN\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"GOLD\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"SHIELD\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"SILVER\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"SWORD\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"account\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"id\",\"type\": \"uint256\"}],\"name\": \"balanceOf\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address[]\",\"name\": \"accounts\",\"type\": \"address[]\"},{\"internalType\": \"uint256[]\",\"name\": \"ids\",\"type\": \"uint256[]\"}],\"name\": \"balanceOfBatch\",\"outputs\": [{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"account\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"}],\"name\": \"isApprovedForAll\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256[]\",\"name\": \"ids\",\"type\": \"uint256[]\"},{\"internalType\": \"uint256[]\",\"name\": \"amounts\",\"type\": \"uint256[]\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"name\": \"safeBatchTransferFrom\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"from\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"id\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"amount\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"name\": \"safeTransferFrom\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"operator\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"approved\",\"type\": \"bool\"}],\"name\": \"setApprovalForAll\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes4\",\"name\": \"interfaceId\",\"type\": \"bytes4\"}],\"name\": \"supportsInterface\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"name\": \"uri\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\"}]", + "bin": "60806040523480156200001157600080fd5b5060405162003703380380620037038339818101604052810190620000379190620006ac565b604051806060016040528060248152602001620036df602491396200006281620001c260201b60201c565b50620000a8817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6103e860405180602001604052806000815250620001d760201b60201c565b620000ed817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6103e860405180602001604052806000815250620001d760201b60201c565b62000132817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6103e860405180602001604052806000815250620001d760201b60201c565b62000177817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6103e860405180602001604052806000815250620001d760201b60201c565b620001bb817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb600160405180602001604052806000815250620001d760201b60201c565b5062000ff5565b8060029081620001d3919062000958565b5050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160362000249576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620002409062000ac6565b60405180910390fd5b60006200025b6200039b60201b60201c565b905062000294816000876200027688620003a360201b60201c565b6200028788620003a360201b60201c565b876200042460201b60201c565b8260008086815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620002f5919062000b17565b925050819055508473ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6287876040516200037592919062000b63565b60405180910390a462000394816000878787876200042c60201b60201c565b5050505050565b600033905090565b60606000600167ffffffffffffffff811115620003c557620003c4620006e9565b5b604051908082528060200260200182016040528015620003f45781602001602082028036833780820191505090505b50905082816000815181106200040f576200040e62000b90565b5b60200260200101818152505080915050919050565b505050505050565b620004588473ffffffffffffffffffffffffffffffffffffffff166200062560201b620008a41760201c565b156200061d578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401620004a195949392919062000c6a565b6020604051808303816000875af1925050508015620004e057506040513d601f19601f82011682018060405250810190620004dd919062000d2b565b60015b6200059157620004ef62000d6a565b806308c379a0036200055257506200050662000dc5565b8062000513575062000554565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000549919062000ea1565b60405180910390fd5b505b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620005889062000f3b565b60405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146200061b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620006129062000fd3565b60405180910390fd5b505b505050505050565b600080823b905060008111915050919050565b6000604051905090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620006748262000647565b9050919050565b620006868162000667565b81146200069257600080fd5b50565b600081519050620006a6816200067b565b92915050565b600060208284031215620006c557620006c462000642565b5b6000620006d58482850162000695565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200076057607f821691505b60208210810362000776576200077562000718565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620007e07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620007a1565b620007ec8683620007a1565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600062000839620008336200082d8462000804565b6200080e565b62000804565b9050919050565b6000819050919050565b620008558362000818565b6200086d620008648262000840565b848454620007ae565b825550505050565b600090565b6200088462000875565b620008918184846200084a565b505050565b5b81811015620008b957620008ad6000826200087a565b60018101905062000897565b5050565b601f8211156200090857620008d2816200077c565b620008dd8462000791565b81016020851015620008ed578190505b62000905620008fc8562000791565b83018262000896565b50505b505050565b600082821c905092915050565b60006200092d600019846008026200090d565b1980831691505092915050565b60006200094883836200091a565b9150826002028217905092915050565b6200096382620006de565b67ffffffffffffffff8111156200097f576200097e620006e9565b5b6200098b825462000747565b62000998828285620008bd565b600060209050601f831160018114620009d05760008415620009bb578287015190505b620009c785826200093a565b86555062000a37565b601f198416620009e0866200077c565b60005b8281101562000a0a57848901518255600182019150602085019450602081019050620009e3565b8683101562000a2a578489015162000a26601f8916826200091a565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f455243313135353a206d696e7420746f20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b600062000aae60218362000a3f565b915062000abb8262000a50565b604082019050919050565b6000602082019050818103600083015262000ae18162000a9f565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000b248262000804565b915062000b318362000804565b925082820190508082111562000b4c5762000b4b62000ae8565b5b92915050565b62000b5d8162000804565b82525050565b600060408201905062000b7a600083018562000b52565b62000b89602083018462000b52565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b62000bca8162000667565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101562000c0c57808201518184015260208101905062000bef565b60008484015250505050565b6000601f19601f8301169050919050565b600062000c368262000bd0565b62000c42818562000bdb565b935062000c5481856020860162000bec565b62000c5f8162000c18565b840191505092915050565b600060a08201905062000c81600083018862000bbf565b62000c90602083018762000bbf565b62000c9f604083018662000b52565b62000cae606083018562000b52565b818103608083015262000cc2818462000c29565b90509695505050505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b62000d058162000cce565b811462000d1157600080fd5b50565b60008151905062000d258162000cfa565b92915050565b60006020828403121562000d445762000d4362000642565b5b600062000d548482850162000d14565b91505092915050565b60008160e01c9050919050565b600060033d111562000d8c5760046000803e62000d8960005162000d5d565b90505b90565b62000d9a8262000c18565b810181811067ffffffffffffffff8211171562000dbc5762000dbb620006e9565b5b80604052505050565b600060443d1062000e5d5762000dda62000638565b60043d036004823e80513d602482011167ffffffffffffffff8211171562000e0457505062000e5d565b808201805167ffffffffffffffff81111562000e24575050505062000e5d565b80602083010160043d03850181111562000e4357505050505062000e5d565b62000e548260200185018662000d8f565b82955050505050505b90565b600062000e6d82620006de565b62000e79818562000a3f565b935062000e8b81856020860162000bec565b62000e968162000c18565b840191505092915050565b6000602082019050818103600083015262000ebd818462000e60565b905092915050565b7f455243313135353a207472616e7366657220746f206e6f6e204552433131353560008201527f526563656976657220696d706c656d656e746572000000000000000000000000602082015250565b600062000f2360348362000a3f565b915062000f308262000ec5565b604082019050919050565b6000602082019050818103600083015262000f568162000f14565b9050919050565b7f455243313135353a204552433131353552656365697665722072656a6563746560008201527f6420746f6b656e73000000000000000000000000000000000000000000000000602082015250565b600062000fbb60288362000a3f565b915062000fc88262000f5d565b604082019050919050565b6000602082019050818103600083015262000fee8162000fac565b9050919050565b6126da80620010056000396000f3fe608060405234801561001057600080fd5b50600436106100ce5760003560e01c80633e4bee381161008c578063a22cb46511610066578063a22cb46514610227578063e3e55f0814610243578063e985e9c514610261578063f242432a14610291576100ce565b80633e4bee38146101bb5780634e1273f4146101d95780635b2725ed14610209576100ce565b8062fdd58e146100d357806301ffc9a7146101035780630e89341c1461013357806313dc989f1461016357806318455da1146101815780632eb2c2d61461019f575b600080fd5b6100ed60048036038101906100e89190611501565b6102ad565b6040516100fa9190611550565b60405180910390f35b61011d600480360381019061011891906115c3565b610375565b60405161012a919061160b565b60405180910390f35b61014d60048036038101906101489190611626565b610457565b60405161015a91906116e3565b60405180910390f35b61016b6104eb565b6040516101789190611550565b60405180910390f35b61018961050f565b6040516101969190611550565b60405180910390f35b6101b960048036038101906101b49190611902565b610533565b005b6101c36105d4565b6040516101d09190611550565b60405180910390f35b6101f360048036038101906101ee9190611a94565b6105f8565b6040516102009190611bca565b60405180910390f35b610211610711565b60405161021e9190611550565b60405180910390f35b610241600480360381019061023c9190611c18565b610735565b005b61024b61074b565b6040516102589190611550565b60405180910390f35b61027b60048036038101906102769190611c58565b61076f565b604051610288919061160b565b60405180910390f35b6102ab60048036038101906102a69190611c98565b610803565b005b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361031d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161031490611da1565b60405180910390fd5b60008083815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b60007fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061044057507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80610450575061044f826108b7565b5b9050919050565b60606002805461046690611df0565b80601f016020809104026020016040519081016040528092919081815260200182805461049290611df0565b80156104df5780601f106104b4576101008083540402835291602001916104df565b820191906000526020600020905b8154815290600101906020018083116104c257829003601f168201915b50505050509050919050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd81565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb81565b61053b610921565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16148061058157506105808561057b610921565b61076f565b5b6105c0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105b790611e93565b60405180910390fd5b6105cd8585858585610929565b5050505050565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81565b6060815183511461063e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161063590611f25565b60405180910390fd5b6000835167ffffffffffffffff81111561065b5761065a61170a565b5b6040519080825280602002602001820160405280156106895781602001602082028036833780820191505090505b50905060005b8451811015610706576106d68582815181106106ae576106ad611f45565b5b60200260200101518583815181106106c9576106c8611f45565b5b60200260200101516102ad565b8282815181106106e9576106e8611f45565b5b602002602001018181525050806106ff90611fa3565b905061068f565b508091505092915050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc81565b610747610740610921565b8383610c3c565b5050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b61080b610921565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16148061085157506108508561084b610921565b61076f565b5b610890576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108879061205d565b60405180910390fd5b61089d8585858585610da8565b5050505050565b600080823b905060008111915050919050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600033905090565b815183511461096d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610964906120ef565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036109dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109d390612181565b60405180910390fd5b60006109e6610921565b90506109f6818787878787611029565b60005b8451811015610ba7576000858281518110610a1757610a16611f45565b5b602002602001015190506000858381518110610a3657610a35611f45565b5b60200260200101519050600080600084815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610ad7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ace90612213565b60405180910390fd5b81810360008085815260200190815260200160002060008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508160008085815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b8c9190612233565b9250508190555050505080610ba090611fa3565b90506109f9565b508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051610c1e929190612267565b60405180910390a4610c34818787878787611031565b505050505050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610caa576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ca190612310565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610d9b919061160b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610e17576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e0e90612181565b60405180910390fd5b6000610e21610921565b9050610e41818787610e3288611208565b610e3b88611208565b87611029565b600080600086815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905083811015610ed8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ecf90612213565b60405180910390fd5b83810360008087815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508360008087815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610f8d9190612233565b925050819055508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62888860405161100a929190612330565b60405180910390a4611020828888888888611282565b50505050505050565b505050505050565b6110508473ffffffffffffffffffffffffffffffffffffffff166108a4565b15611200578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016110969594939291906123bd565b6020604051808303816000875af19250505080156110d257506040513d601f19601f820116820180604052508101906110cf919061243a565b60015b611177576110de612474565b806308c379a00361113a57506110f2612496565b806110fd575061113c565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161113191906116e3565b60405180910390fd5b505b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161116e90612598565b60405180910390fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146111fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111f59061262a565b60405180910390fd5b505b505050505050565b60606000600167ffffffffffffffff8111156112275761122661170a565b5b6040519080825280602002602001820160405280156112555781602001602082028036833780820191505090505b509050828160008151811061126d5761126c611f45565b5b60200260200101818152505080915050919050565b6112a18473ffffffffffffffffffffffffffffffffffffffff166108a4565b15611451578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b81526004016112e795949392919061264a565b6020604051808303816000875af192505050801561132357506040513d601f19601f82011682018060405250810190611320919061243a565b60015b6113c85761132f612474565b806308c379a00361138b5750611343612496565b8061134e575061138d565b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161138291906116e3565b60405180910390fd5b505b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113bf90612598565b60405180910390fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461144f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114469061262a565b60405180910390fd5b505b505050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006114988261146d565b9050919050565b6114a88161148d565b81146114b357600080fd5b50565b6000813590506114c58161149f565b92915050565b6000819050919050565b6114de816114cb565b81146114e957600080fd5b50565b6000813590506114fb816114d5565b92915050565b6000806040838503121561151857611517611463565b5b6000611526858286016114b6565b9250506020611537858286016114ec565b9150509250929050565b61154a816114cb565b82525050565b60006020820190506115656000830184611541565b92915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6115a08161156b565b81146115ab57600080fd5b50565b6000813590506115bd81611597565b92915050565b6000602082840312156115d9576115d8611463565b5b60006115e7848285016115ae565b91505092915050565b60008115159050919050565b611605816115f0565b82525050565b600060208201905061162060008301846115fc565b92915050565b60006020828403121561163c5761163b611463565b5b600061164a848285016114ec565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561168d578082015181840152602081019050611672565b60008484015250505050565b6000601f19601f8301169050919050565b60006116b582611653565b6116bf818561165e565b93506116cf81856020860161166f565b6116d881611699565b840191505092915050565b600060208201905081810360008301526116fd81846116aa565b905092915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61174282611699565b810181811067ffffffffffffffff821117156117615761176061170a565b5b80604052505050565b6000611774611459565b90506117808282611739565b919050565b600067ffffffffffffffff8211156117a05761179f61170a565b5b602082029050602081019050919050565b600080fd5b60006117c96117c484611785565b61176a565b905080838252602082019050602084028301858111156117ec576117eb6117b1565b5b835b81811015611815578061180188826114ec565b8452602084019350506020810190506117ee565b5050509392505050565b600082601f83011261183457611833611705565b5b81356118448482602086016117b6565b91505092915050565b600080fd5b600067ffffffffffffffff82111561186d5761186c61170a565b5b61187682611699565b9050602081019050919050565b82818337600083830152505050565b60006118a56118a084611852565b61176a565b9050828152602081018484840111156118c1576118c061184d565b5b6118cc848285611883565b509392505050565b600082601f8301126118e9576118e8611705565b5b81356118f9848260208601611892565b91505092915050565b600080600080600060a0868803121561191e5761191d611463565b5b600061192c888289016114b6565b955050602061193d888289016114b6565b945050604086013567ffffffffffffffff81111561195e5761195d611468565b5b61196a8882890161181f565b935050606086013567ffffffffffffffff81111561198b5761198a611468565b5b6119978882890161181f565b925050608086013567ffffffffffffffff8111156119b8576119b7611468565b5b6119c4888289016118d4565b9150509295509295909350565b600067ffffffffffffffff8211156119ec576119eb61170a565b5b602082029050602081019050919050565b6000611a10611a0b846119d1565b61176a565b90508083825260208201905060208402830185811115611a3357611a326117b1565b5b835b81811015611a5c5780611a4888826114b6565b845260208401935050602081019050611a35565b5050509392505050565b600082601f830112611a7b57611a7a611705565b5b8135611a8b8482602086016119fd565b91505092915050565b60008060408385031215611aab57611aaa611463565b5b600083013567ffffffffffffffff811115611ac957611ac8611468565b5b611ad585828601611a66565b925050602083013567ffffffffffffffff811115611af657611af5611468565b5b611b028582860161181f565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b611b41816114cb565b82525050565b6000611b538383611b38565b60208301905092915050565b6000602082019050919050565b6000611b7782611b0c565b611b818185611b17565b9350611b8c83611b28565b8060005b83811015611bbd578151611ba48882611b47565b9750611baf83611b5f565b925050600181019050611b90565b5085935050505092915050565b60006020820190508181036000830152611be48184611b6c565b905092915050565b611bf5816115f0565b8114611c0057600080fd5b50565b600081359050611c1281611bec565b92915050565b60008060408385031215611c2f57611c2e611463565b5b6000611c3d858286016114b6565b9250506020611c4e85828601611c03565b9150509250929050565b60008060408385031215611c6f57611c6e611463565b5b6000611c7d858286016114b6565b9250506020611c8e858286016114b6565b9150509250929050565b600080600080600060a08688031215611cb457611cb3611463565b5b6000611cc2888289016114b6565b9550506020611cd3888289016114b6565b9450506040611ce4888289016114ec565b9350506060611cf5888289016114ec565b925050608086013567ffffffffffffffff811115611d1657611d15611468565b5b611d22888289016118d4565b9150509295509295909350565b7f455243313135353a2062616c616e636520717565727920666f7220746865207a60008201527f65726f2061646472657373000000000000000000000000000000000000000000602082015250565b6000611d8b602b8361165e565b9150611d9682611d2f565b604082019050919050565b60006020820190508181036000830152611dba81611d7e565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680611e0857607f821691505b602082108103611e1b57611e1a611dc1565b5b50919050565b7f455243313135353a207472616e736665722063616c6c6572206973206e6f742060008201527f6f776e6572206e6f7220617070726f7665640000000000000000000000000000602082015250565b6000611e7d60328361165e565b9150611e8882611e21565b604082019050919050565b60006020820190508181036000830152611eac81611e70565b9050919050565b7f455243313135353a206163636f756e747320616e6420696473206c656e67746860008201527f206d69736d617463680000000000000000000000000000000000000000000000602082015250565b6000611f0f60298361165e565b9150611f1a82611eb3565b604082019050919050565b60006020820190508181036000830152611f3e81611f02565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000611fae826114cb565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611fe057611fdf611f74565b5b600182019050919050565b7f455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7260008201527f20617070726f7665640000000000000000000000000000000000000000000000602082015250565b600061204760298361165e565b915061205282611feb565b604082019050919050565b600060208201905081810360008301526120768161203a565b9050919050565b7f455243313135353a2069647320616e6420616d6f756e7473206c656e6774682060008201527f6d69736d61746368000000000000000000000000000000000000000000000000602082015250565b60006120d960288361165e565b91506120e48261207d565b604082019050919050565b60006020820190508181036000830152612108816120cc565b9050919050565b7f455243313135353a207472616e7366657220746f20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061216b60258361165e565b91506121768261210f565b604082019050919050565b6000602082019050818103600083015261219a8161215e565b9050919050565b7f455243313135353a20696e73756666696369656e742062616c616e636520666f60008201527f72207472616e7366657200000000000000000000000000000000000000000000602082015250565b60006121fd602a8361165e565b9150612208826121a1565b604082019050919050565b6000602082019050818103600083015261222c816121f0565b9050919050565b600061223e826114cb565b9150612249836114cb565b925082820190508082111561226157612260611f74565b5b92915050565b600060408201905081810360008301526122818185611b6c565b905081810360208301526122958184611b6c565b90509392505050565b7f455243313135353a2073657474696e6720617070726f76616c2073746174757360008201527f20666f722073656c660000000000000000000000000000000000000000000000602082015250565b60006122fa60298361165e565b91506123058261229e565b604082019050919050565b60006020820190508181036000830152612329816122ed565b9050919050565b60006040820190506123456000830185611541565b6123526020830184611541565b9392505050565b6123628161148d565b82525050565b600081519050919050565b600082825260208201905092915050565b600061238f82612368565b6123998185612373565b93506123a981856020860161166f565b6123b281611699565b840191505092915050565b600060a0820190506123d26000830188612359565b6123df6020830187612359565b81810360408301526123f18186611b6c565b905081810360608301526124058185611b6c565b905081810360808301526124198184612384565b90509695505050505050565b60008151905061243481611597565b92915050565b6000602082840312156124505761244f611463565b5b600061245e84828501612425565b91505092915050565b60008160e01c9050919050565b600060033d11156124935760046000803e612490600051612467565b90505b90565b600060443d10612523576124a8611459565b60043d036004823e80513d602482011167ffffffffffffffff821117156124d0575050612523565b808201805167ffffffffffffffff8111156124ee5750505050612523565b80602083010160043d03850181111561250b575050505050612523565b61251a82602001850186611739565b82955050505050505b90565b7f455243313135353a207472616e7366657220746f206e6f6e204552433131353560008201527f526563656976657220696d706c656d656e746572000000000000000000000000602082015250565b600061258260348361165e565b915061258d82612526565b604082019050919050565b600060208201905081810360008301526125b181612575565b9050919050565b7f455243313135353a204552433131353552656365697665722072656a6563746560008201527f6420746f6b656e73000000000000000000000000000000000000000000000000602082015250565b600061261460288361165e565b915061261f826125b8565b604082019050919050565b6000602082019050818103600083015261264381612607565b9050919050565b600060a08201905061265f6000830188612359565b61266c6020830187612359565b6126796040830186611541565b6126866060830185611541565b81810360808301526126988184612384565b9050969550505050505056fea26469706673582212202efe4fe4b15dfb85b9c4e8232dd98da05259f65aeb7d1ecc6d56102e7ca2994164736f6c6343000811003368747470733a2f2f6578616d706c652e636f6d2f6173736574732f7b69647d2e6a736f6e" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/4-nft1155.sol b/integration_test_util/compiled_contracts/4-nft1155.sol new file mode 100644 index 0000000000..fb2371dbb4 --- /dev/null +++ b/integration_test_util/compiled_contracts/4-nft1155.sol @@ -0,0 +1,993 @@ +// File: @openzeppelin/contracts/utils/introspection/IERC165.sol + +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/IERC1155.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Required interface of an ERC1155 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1155[EIP]. + * + * _Available since v3.1._ + */ +interface IERC1155 is IERC165 { + /** + * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all + * transfers. + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + * `approved`. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + * + * If an {URI} event was emitted for `id`, the standard + * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + * returned by {IERC1155MetadataURI-uri}. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Returns the amount of tokens of token type `id` owned by `account`. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) + external + view + returns (uint256[] memory); + + /** + * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the caller. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. + * + * See {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) external; +} + +// File: @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + @dev Handles the receipt of a single ERC1155 token type. This function is + called at the end of a `safeTransferFrom` after the balance has been updated. + To accept the transfer, this must return + `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + (i.e. 0xf23a6e61, or its own function selector). + @param operator The address which initiated the transfer (i.e. msg.sender) + @param from The address which previously owned the token + @param id The ID of the token being transferred + @param value The amount of tokens being transferred + @param data Additional data with no specified format + @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + @dev Handles the receipt of a multiple ERC1155 token types. This function + is called at the end of a `safeBatchTransferFrom` after the balances have + been updated. To accept the transfer(s), this must return + `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + (i.e. 0xbc197c81, or its own function selector). + @param operator The address which initiated the batch transfer (i.e. msg.sender) + @param from The address which previously owned the token + @param ids An array containing ids of each token being transferred (order and length must match values array) + @param values An array containing amounts of each token being transferred (order and length must match ids array) + @param data Additional data with no specified format + @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + +// File: @openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the optional ERC1155MetadataExtension interface, as defined + * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP]. + * + * _Available since v3.1._ + */ +interface IERC1155MetadataURI is IERC1155 { + /** + * @dev Returns the URI for token type `id`. + * + * If the `\{id\}` substring is present in the URI, it must be replaced by + * clients with the actual token type ID. + */ + function uri(uint256 id) external view returns (string memory); +} + +// File: @openzeppelin/contracts/utils/Address.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/Address.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: @openzeppelin/contracts/utils/Context.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts/utils/introspection/ERC165.sol + + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + +// File: @openzeppelin/contracts/token/ERC1155/ERC1155.sol + + +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/ERC1155.sol) + +pragma solidity ^0.8.0; + + + + + + +/** + * @dev Implementation of the basic standard multi-token. + * See https://eips.ethereum.org/EIPS/eip-1155 + * Originally based on code by Enjin: https://github.com/enjin/erc-1155 + * + * _Available since v3.1._ + */ +contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { + using Address for address; + + // Mapping from token ID to account balances + mapping(uint256 => mapping(address => uint256)) private _balances; + + // Mapping from account to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json + string private _uri; + + /** + * @dev See {_setURI}. + */ + constructor(string memory uri_) { + _setURI(uri_); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the same URI for *all* token types. It relies + * on the token type ID substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * Clients calling this function must replace the `\{id\}` substring with the + * actual token type ID. + */ + function uri(uint256) public view virtual override returns (string memory) { + return _uri; + } + + /** + * @dev See {IERC1155-balanceOf}. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + require(account != address(0), "ERC1155: balance query for the zero address"); + return _balances[id][account]; + } + + /** + * @dev See {IERC1155-balanceOfBatch}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + public + view + virtual + override + returns (uint256[] memory) + { + require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + + return batchBalances; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC1155-isApprovedForAll}. + */ + function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual override { + require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: caller is not owner nor approved" + ); + _safeTransferFrom(from, to, id, amount, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual override { + require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: transfer caller is not owner nor approved" + ); + _safeBatchTransferFrom(from, to, ids, amounts, data); + } + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data); + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + + emit TransferSingle(operator, from, to, id, amount); + + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, to, ids, amounts, data); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + } + + /** + * @dev Sets a new URI for all token types, by relying on the token type ID + * substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * By this mechanism, any occurrence of the `\{id\}` substring in either the + * URI or any of the amounts in the JSON file at said URI will be replaced by + * clients with the token type ID. + * + * For example, the `https://token-cdn-domain/\{id\}.json` URI would be + * interpreted by clients as + * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + * for token type ID 0x4cce0. + * + * See {uri}. + * + * Because these URIs cannot be meaningfully represented by the {URI} event, + * this function emits no events. + */ + function _setURI(string memory newuri) internal virtual { + _uri = newuri; + } + + /** + * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, address(0), to, _asSingletonArray(id), _asSingletonArray(amount), data); + + _balances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + + _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); + + for (uint256 i = 0; i < ids.length; i++) { + _balances[ids[i]][to] += amounts[i]; + } + + emit TransferBatch(operator, address(0), to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); + } + + /** + * @dev Destroys `amount` tokens of token type `id` from `from` + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must have at least `amount` tokens of token type `id`. + */ + function _burn( + address from, + uint256 id, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, address(0), _asSingletonArray(id), _asSingletonArray(amount), ""); + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + + emit TransferSingle(operator, from, address(0), id, amount); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + */ + function _burnBatch( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); + + for (uint256 i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + } + + emit TransferBatch(operator, from, address(0), ids, amounts); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits a {ApprovalForAll} event. + */ + function _setApprovalForAll( + address owner, + address operator, + bool approved + ) internal virtual { + require(owner != operator, "ERC1155: setting approval status for self"); + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning, as well as batched variants. + * + * The same hook is called on both single and batched variants. For single + * transfers, the length of the `id` and `amount` arrays will be 1. + * + * Calling conditions (for each `id` and `amount` pair): + * + * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * of token type `id` will be transferred to `to`. + * - When `from` is zero, `amount` tokens of token type `id` will be minted + * for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` + * will be burned. + * - `from` and `to` are never both zero. + * - `ids` and `amounts` have the same, non-zero length. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual {} + + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) private { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) private { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( + bytes4 response + ) { + if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { + uint256[] memory array = new uint256[](1); + array[0] = element; + + return array; + } +} + +// File: ERC-1155.sol + + +pragma solidity ^0.8.4; + +contract Nft1155 is ERC1155 { + uint256 public constant GOLD = 2**256 - 1; + uint256 public constant SILVER = 2**256 - 2; + uint256 public constant SWORD = 2**256 - 3; + uint256 public constant SHIELD = 2**256 - 4; + uint256 public constant CROWN = 2**256 - 5; + + constructor(address rich) ERC1155("https://example.com/assets/{id}.json") { + _mint(rich, GOLD, 1000, ""); + _mint(rich, SILVER, 1000, ""); + _mint(rich, SWORD, 1000, ""); + _mint(rich, SHIELD, 1000, ""); + _mint(rich, CROWN, 1, ""); + } +} diff --git a/integration_test_util/compiled_contracts/5-create-Bar.json b/integration_test_util/compiled_contracts/5-create-Bar.json new file mode 100644 index 0000000000..f931c04003 --- /dev/null +++ b/integration_test_util/compiled_contracts/5-create-Bar.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"anonymous\": false,\"inputs\": [],\"name\": \"Deployed\",\"type\": \"event\"},{\"inputs\": [],\"name\": \"deploy\",\"outputs\": [{\"internalType\": \"contract Foo\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"}],\"name\": \"deploy2\",\"outputs\": [{\"internalType\": \"contract Foo\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"}]", + "bin": "608060405234801561001057600080fd5b5061031e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063775c300c1461003b578063dec3c14f14610059575b600080fd5b610043610089565b60405161005091906101dd565b60405180910390f35b610073600480360381019061006e9190610233565b6100ea565b60405161008091906101dd565b60405180910390f35b60008060405161009890610152565b604051809103906000f0801580156100b4573d6000803e3d6000fd5b5090507f3fad920548ed9f22deb8333b4cc1e4f9bc36666a1c2aa30ad59a0a3bb9dcbb9260405160405180910390a18091505090565b600080826040516100fa90610152565b8190604051809103906000f590508015801561011a573d6000803e3d6000fd5b5090507f3fad920548ed9f22deb8333b4cc1e4f9bc36666a1c2aa30ad59a0a3bb9dcbb9260405160405180910390a180915050919050565b60888061026183390190565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006101a361019e6101998461015e565b61017e565b61015e565b9050919050565b60006101b582610188565b9050919050565b60006101c7826101aa565b9050919050565b6101d7816101bc565b82525050565b60006020820190506101f260008301846101ce565b92915050565b600080fd5b6000819050919050565b610210816101fd565b811461021b57600080fd5b50565b60008135905061022d81610207565b92915050565b600060208284031215610249576102486101f8565b5b60006102578482850161021e565b9150509291505056fe6080604052348015600f57600080fd5b507f8fb1589b9d067960d146a77011bc245c8ec9e10549841f3be246420a15652ede60405160405180910390a1603f8060496000396000f3fe6080604052600080fdfea2646970667358221220b4474c3106fa705a86b306575def378add858b94b1754d595b892938c3e5cb1864736f6c63430008110033a2646970667358221220915581525513cb9de60d7ee3c4fb4b9e6b6e51fa9be5b9edbe601f2ca85a927564736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/5-create-BarInteraction.json b/integration_test_util/compiled_contracts/5-create-BarInteraction.json new file mode 100644 index 0000000000..9c8bc80d12 --- /dev/null +++ b/integration_test_util/compiled_contracts/5-create-BarInteraction.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [],\"name\": \"deploy\",\"outputs\": [{\"internalType\": \"contract Foo\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"}],\"name\": \"deploy2\",\"outputs\": [{\"internalType\": \"contract Foo\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_bar\",\"type\": \"address\"}],\"name\": \"setBarAddr\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"}]", + "bin": "608060405234801561001057600080fd5b50610477806100206000396000f3fe6080604052600436106100345760003560e01c80632b8999b614610039578063775c300c14610055578063dec3c14f14610080575b600080fd5b610053600480360381019061004e91906102a2565b6100bd565b005b34801561006157600080fd5b5061006a610100565b604051610077919061032e565b60405180910390f35b34801561008c57600080fd5b506100a760048036038101906100a2919061037f565b610199565b6040516100b4919061032e565b60405180910390f35b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663775c300c6040518163ffffffff1660e01b81526004016020604051808303816000875af1158015610170573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061019491906103ea565b905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663dec3c14f836040518263ffffffff1660e01b81526004016101f59190610426565b6020604051808303816000875af1158015610214573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023891906103ea565b9050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061026f82610244565b9050919050565b61027f81610264565b811461028a57600080fd5b50565b60008135905061029c81610276565b92915050565b6000602082840312156102b8576102b761023f565b5b60006102c68482850161028d565b91505092915050565b6000819050919050565b60006102f46102ef6102ea84610244565b6102cf565b610244565b9050919050565b6000610306826102d9565b9050919050565b6000610318826102fb565b9050919050565b6103288161030d565b82525050565b6000602082019050610343600083018461031f565b92915050565b6000819050919050565b61035c81610349565b811461036757600080fd5b50565b60008135905061037981610353565b92915050565b6000602082840312156103955761039461023f565b5b60006103a38482850161036a565b91505092915050565b60006103b782610264565b9050919050565b6103c7816103ac565b81146103d257600080fd5b50565b6000815190506103e4816103be565b92915050565b600060208284031215610400576103ff61023f565b5b600061040e848285016103d5565b91505092915050565b61042081610349565b82525050565b600060208201905061043b6000830184610417565b9291505056fea26469706673582212202792dcfd0ef10f11741939b27aa6a40123f0b826535f82d6a7105fdf2390498564736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/5-create-Foo.json b/integration_test_util/compiled_contracts/5-create-Foo.json new file mode 100644 index 0000000000..73f5006889 --- /dev/null +++ b/integration_test_util/compiled_contracts/5-create-Foo.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"anonymous\": false,\"inputs\": [],\"name\": \"ConstructorCall\",\"type\": \"event\"}]", + "bin": "6080604052348015600f57600080fd5b507f8fb1589b9d067960d146a77011bc245c8ec9e10549841f3be246420a15652ede60405160405180910390a1603f8060496000396000f3fe6080604052600080fdfea2646970667358221220b4474c3106fa705a86b306575def378add858b94b1754d595b892938c3e5cb1864736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/5-create.json b/integration_test_util/compiled_contracts/5-create.json new file mode 100644 index 0000000000..9c8bc80d12 --- /dev/null +++ b/integration_test_util/compiled_contracts/5-create.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\": [],\"name\": \"deploy\",\"outputs\": [{\"internalType\": \"contract Foo\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"}],\"name\": \"deploy2\",\"outputs\": [{\"internalType\": \"contract Foo\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_bar\",\"type\": \"address\"}],\"name\": \"setBarAddr\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"}]", + "bin": "608060405234801561001057600080fd5b50610477806100206000396000f3fe6080604052600436106100345760003560e01c80632b8999b614610039578063775c300c14610055578063dec3c14f14610080575b600080fd5b610053600480360381019061004e91906102a2565b6100bd565b005b34801561006157600080fd5b5061006a610100565b604051610077919061032e565b60405180910390f35b34801561008c57600080fd5b506100a760048036038101906100a2919061037f565b610199565b6040516100b4919061032e565b60405180910390f35b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663775c300c6040518163ffffffff1660e01b81526004016020604051808303816000875af1158015610170573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061019491906103ea565b905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663dec3c14f836040518263ffffffff1660e01b81526004016101f59190610426565b6020604051808303816000875af1158015610214573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023891906103ea565b9050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061026f82610244565b9050919050565b61027f81610264565b811461028a57600080fd5b50565b60008135905061029c81610276565b92915050565b6000602082840312156102b8576102b761023f565b5b60006102c68482850161028d565b91505092915050565b6000819050919050565b60006102f46102ef6102ea84610244565b6102cf565b610244565b9050919050565b6000610306826102d9565b9050919050565b6000610318826102fb565b9050919050565b6103288161030d565b82525050565b6000602082019050610343600083018461031f565b92915050565b6000819050919050565b61035c81610349565b811461036757600080fd5b50565b60008135905061037981610353565b92915050565b6000602082840312156103955761039461023f565b5b60006103a38482850161036a565b91505092915050565b60006103b782610264565b9050919050565b6103c7816103ac565b81146103d257600080fd5b50565b6000815190506103e4816103be565b92915050565b600060208284031215610400576103ff61023f565b5b600061040e848285016103d5565b91505092915050565b61042081610349565b82525050565b600060208201905061043b6000830184610417565b9291505056fea26469706673582212202792dcfd0ef10f11741939b27aa6a40123f0b826535f82d6a7105fdf2390498564736f6c63430008110033" +} \ No newline at end of file diff --git a/integration_test_util/compiled_contracts/5-create.sol b/integration_test_util/compiled_contracts/5-create.sol new file mode 100644 index 0000000000..8030801964 --- /dev/null +++ b/integration_test_util/compiled_contracts/5-create.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract Foo { + event ConstructorCall(); + + constructor() { + emit ConstructorCall(); + } +} + +pragma solidity >=0.7.0 <0.9.0; + +contract Bar { + event Deployed(); + + function deploy() public returns(Foo) { + Foo ret = new Foo(); + + emit Deployed(); + return ret; + } + + function deploy2(bytes32 salt) public returns(Foo) { + Foo ret = new Foo{ salt: salt }(); + + emit Deployed(); + return ret; + } +} + +contract BarInteraction { + address barAddr; + + function setBarAddr(address _bar) public payable { + barAddr = _bar; + } + + function deploy() public returns(Foo) { + return Bar(barAddr).deploy(); + } + + function deploy2(bytes32 salt) public returns(Foo) { + return Bar(barAddr).deploy2(salt); + } +} \ No newline at end of file diff --git a/integration_test_util/demo/integration_test_demo_bank_test.go b/integration_test_util/demo/integration_test_demo_bank_test.go new file mode 100644 index 0000000000..f86a47f7ac --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_bank_test.go @@ -0,0 +1,70 @@ +package demo + +import ( + "github.com/EscanBE/evermint/v12/integration_test_util" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_MintCoins() { + newAccount := integration_test_util.NewTestAccount(suite.T(), nil) + + balance := suite.CITS.QueryBalance(0, newAccount.GetCosmosAddress().String()) + suite.Require().True(balance.Amount.Equal(sdk.ZeroInt())) + + mintCoin := sdk.NewCoin(suite.CITS.ChainConstantsConfig.GetMinDenom(), suite.CITS.TestConfig.InitBalanceAmount) + suite.CITS.MintCoin(newAccount, mintCoin) + suite.Commit() + + balance = suite.CITS.QueryBalance(0, newAccount.GetCosmosAddress().String()) + suite.Require().True(balance.Amount.Equal(mintCoin.Amount)) +} + +func (suite *DemoTestSuite) Test_QC_Bank_Balance() { + balance := suite.CITS.QueryBalance(0, suite.CITS.WalletAccounts.Number(1).GetCosmosAddress().String()) + + suite.Require().True(balance.Amount.GT(sdk.ZeroInt())) + suite.Equal(suite.CITS.TestConfig.InitBalanceAmount, balance.Amount) + + secondaryBalance := suite.CITS.QueryBalanceByDenom( + 0, + suite.CITS.WalletAccounts.Number(1).GetCosmosAddress().String(), + suite.CITS.TestConfig.SecondaryDenomUnits[0].Denom, + ) + + suite.Require().True(secondaryBalance.Amount.GT(sdk.ZeroInt())) + suite.Equal(suite.CITS.TestConfig.InitBalanceAmount, secondaryBalance.Amount) +} + +func (suite *DemoTestSuite) Test_QC_Bank_Balance_At_Different_Blocks() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + senderBalanceBefore := suite.CITS.QueryBalance(0, sender.GetCosmosAddress().String()) + receiverBalanceBefore := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + + suite.Require().Truef(senderBalanceBefore.Amount.GT(sdk.ZeroInt()), "sender must have balance") + + contextHeightBeforeSend := suite.CITS.CurrentContext.BlockHeight() + suite.Commit() + + err := suite.CITS.TxSend(sender, receiver, 0.1) + suite.Commit() + suite.Require().NoError(err) + + senderBalanceAfter := suite.CITS.QueryBalance(0, sender.GetCosmosAddress().String()) + receiverBalanceAfter := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + + suite.NotEqualf(senderBalanceBefore.String(), senderBalanceAfter.String(), "sender balance must be reduced") + suite.Require().Truef(senderBalanceAfter.IsLT(*senderBalanceBefore), "sender balance must be reduced") + + suite.NotEqualf(receiverBalanceBefore.String(), receiverBalanceAfter.String(), "receiver balance must be increased") + suite.Require().Truef(receiverBalanceBefore.IsLT(*receiverBalanceAfter), "receiver balance must be increased") + + // Historical block height + historicalSenderBalance := suite.CITS.QueryBalance(contextHeightBeforeSend, sender.GetCosmosAddress().String()) + historicalReceiverBalanceAfter := suite.CITS.QueryBalance(contextHeightBeforeSend, receiver.GetCosmosAddress().String()) + suite.Equal(senderBalanceBefore.String(), historicalSenderBalance.String(), "mis-match sender balance at historical height") + suite.Equal(receiverBalanceBefore.String(), historicalReceiverBalanceAfter.String(), "mis-match sender balance at historical height") +} diff --git a/integration_test_util/demo/integration_test_demo_contract_test.go b/integration_test_util/demo/integration_test_demo_contract_test.go new file mode 100644 index 0000000000..71e80c4948 --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_contract_test.go @@ -0,0 +1,157 @@ +package demo + +import ( + "github.com/EscanBE/evermint/v12/integration_test_util" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + "github.com/ethereum/go-ethereum/common" + "math/big" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_Contract_DeployContracts() { + suite.Run("ERC-20", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeployErc20Contract(deployer, "coin", "token", 18) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(0), newContractAddress) + suite.assertContractCode(newContractAddress) + }) + + suite.Run("1-create.sol", func() { + deployer := suite.CITS.WalletAccounts.Number(2) + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(0), newContractAddress) + suite.assertContractCode(newContractAddress) + + suite.Run("send a success tx", func() { + data, err := integration_test_util.Contract1Storage.ABI.Pack("store", big.NewInt(7)) + suite.Require().NoError(err) + _, resDeliver, err = suite.CITS.TxSendEvmTx(suite.Ctx(), deployer, &newContractAddress, nil, data) + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Commit() + }) + + suite.Run("send a failed tx", func() { + data, err := integration_test_util.Contract1Storage.ABI.Pack("store", big.NewInt(3)) + suite.Require().NoError(err) + _, resDeliver, err = suite.CITS.TxSendEvmTx(suite.Ctx(), deployer, &newContractAddress, nil, data) + suite.Require().Error(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.NotEmpty(resDeliver.EvmError) + suite.Commit() + }) + }) + + suite.Run("2-wevmos.sol", func() { + deployer := suite.CITS.WalletAccounts.Number(3) + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeploy2WEvmosContract(deployer, nil) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(0), newContractAddress) + suite.assertContractCode(newContractAddress) + }) + + suite.Run("3-nft721.sol", func() { + deployer := suite.CITS.WalletAccounts.Number(4) + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeploy3Nft721Contract(deployer, nil) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(0), newContractAddress) + suite.assertContractCode(newContractAddress) + }) + + suite.Run("4-nft1155.sol", func() { + deployer := suite.CITS.WalletAccounts.Number(5) + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeploy4Nft1155Contract(deployer, nil) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(0), newContractAddress) + suite.assertContractCode(newContractAddress) + }) + + suite.Run("5-create.sol", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + + accDeployer := suite.CITS.ChainApp.AccountKeeper().GetAccount(suite.Ctx(), deployer.GetCosmosAddress()) + var nonce uint64 = accDeployer.GetSequence() + + fooContractAddress, _, resDeliver, err := suite.CITS.TxDeploy5CreateFooContract(deployer) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(nonce), fooContractAddress) + suite.assertContractCode(fooContractAddress) + + nonce++ + + barContractAddress, _, resDeliver, err := suite.CITS.TxDeploy5CreateBarContract(deployer) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(nonce), barContractAddress) + suite.assertContractCode(barContractAddress) + + nonce++ + + barInteractionContractAddress, _, resDeliver, err := suite.CITS.TxDeploy5CreateBarInteractionContract(deployer, barContractAddress) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + suite.Require().Equal(deployer.ComputeContractAddress(nonce), barInteractionContractAddress) + suite.assertContractCode(barInteractionContractAddress) + }) +} + +func (suite *DemoTestSuite) assertContractCode(contractAddress common.Address) { + res, err := suite.CITS.QueryClients.EVM.Code(suite.Ctx(), &evmtypes.QueryCodeRequest{ + Address: contractAddress.String(), + }) + if suite.NoError(err) { + suite.Require().NotEmpty(res.Code) + suite.Require().True(len(res.Code) >= 45) + } +} diff --git a/integration_test_util/demo/integration_test_demo_erc20_test.go b/integration_test_util/demo/integration_test_demo_erc20_test.go new file mode 100644 index 0000000000..a8e4452d8d --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_erc20_test.go @@ -0,0 +1,131 @@ +package demo + +import ( + sdkmath "cosmossdk.io/math" + "fmt" + erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "math" + "strings" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_ERC20_DeployContract() { + deployer := suite.CITS.WalletAccounts.Number(1) + + deployerBalanceBefore := suite.CITS.QueryBalance(0, deployer.GetCosmosAddress().String()) + suite.Require().Truef(deployerBalanceBefore.Amount.GT(sdk.ZeroInt()), "deployer must have balance") + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeployErc20Contract(deployer, "coin", "token", 18) + suite.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.Require().Equal(deployer.ComputeContractAddress(0), newContractAddress) +} + +func (suite *DemoTestSuite) Test_ERC20_RegisterErc20CoinPair() { + proposer := suite.CITS.WalletAccounts.Number(1) + + proposalId := suite.CITS.TxFullRegisterCoin(proposer, suite.CITS.TestConfig.SecondaryDenomUnits[0].Denom) + suite.Commit() + suite.Require().Equal(uint64(1), proposalId) + + tokenPairs, err := suite.CITS.QueryClients.Erc20.TokenPairs(suite.Ctx(), &erc20types.QueryTokenPairsRequest{}) + suite.Require().NoError(err) + suite.Require().NotNil(tokenPairs) + suite.Require().Equal(1, len(tokenPairs.TokenPairs)) + + tokenPair := tokenPairs.TokenPairs[0] + suite.assertContractCode(common.HexToAddress(tokenPair.Erc20Address)) +} + +func (suite *DemoTestSuite) Test_ERC20_RegisterIbcTokenPair() { + suite.SetupIbcTest() + suite.testSetupIbc() + + receiver := suite.CITS.WalletAccounts.Number(1) + proposer := suite.CITS.WalletAccounts.Number(2) + + fromChain := suite.IBCITS.Chain2 + transferCoin := fromChain.NewBaseCoin(1) + packet := suite.IBCITS.TxMakeIbcTransferFromChain2ToChain1(receiver, transferCoin) + + ibcDenom := suite.CITS.QueryDenomHash(packet.GetDestPort(), packet.GetDestChannel(), transferCoin.Denom) + + var ibcDenomDisplay string + if len(transferCoin.Denom) <= 3 { + ibcDenomDisplay = "eth" + } else { + ibcDenomDisplay = strings.ToLower(transferCoin.Denom[1:]) + } + proposalId := suite.CITS.TxFullRegisterCoinWithNewBankMetadata(proposer, ibcDenom, ibcDenomDisplay, uint32(fromChain.ChainConstantsConfig.GetBaseExponent())) + suite.Commit() + suite.Require().Equal(uint64(1), proposalId) + + tokenPairs, err := suite.CITS.QueryClients.Erc20.TokenPairs(suite.Ctx(), &erc20types.QueryTokenPairsRequest{}) + suite.Require().NoError(err) + suite.Require().NotNil(tokenPairs) + suite.Require().Equal(1, len(tokenPairs.TokenPairs)) + + tokenPair := tokenPairs.TokenPairs[0] + suite.Equalf(ibcDenom, tokenPair.Denom, "token pair symbol %s must be equal to ibc denom %s", tokenPair.Denom, ibcDenom) + suite.assertContractCode(common.HexToAddress(tokenPair.Erc20Address)) +} + +func (suite *DemoTestSuite) Test_ERC20_RegisterIbcTokenFromErc20() { + suite.SetupIbcTest() + + deployer := suite.CITS.WalletAccounts.Number(1) + proposer := suite.CITS.WalletAccounts.Number(2) + + const decimal = 6 + + newContractAddress, _, resDeliver, err := suite.CITS.TxDeployErc20Contract(deployer, "coin", "token", decimal) + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + suite.CITS.Commit() + + _ = suite.CITS.TxFullRegisterIbcCoinFromErc20Contract(proposer, newContractAddress) + + tokenPair, err := suite.CITS.QueryFirstErc20TokenPair(true) + suite.Require().NoError(err) + suite.Require().Equal(newContractAddress.String(), tokenPair.Erc20Address) + suite.Require().Equal(fmt.Sprintf("erc20/%s", tokenPair.Erc20Address), tokenPair.Denom) +} + +func (suite *DemoTestSuite) Test_ERC20_TransferErc20IbcToken() { + suite.SetupIbcTest() + + fromChain, _, relayer, _ := suite.IBCITS.Chain(2) + toChain := suite.CITS + deployer := fromChain.WalletAccounts.Number(1) + proposer := fromChain.WalletAccounts.Number(2) + sender := relayer + receiver := toChain.WalletAccounts.Number(1) + + const decimal = 6 + const amount = 100 + + newContractAddress, _, resDeliver, err := fromChain.TxDeployErc20Contract(deployer, "coin", "token", decimal) + suite.Require().NoError(err) + suite.Require().NotNil(resDeliver) + fromChain.Commit() + + _ = fromChain.TxFullRegisterIbcCoinFromErc20Contract(proposer, newContractAddress) + + tokenPair, err := fromChain.QueryFirstErc20TokenPair(true) + suite.Require().NoError(err) + + transferIntAmt := sdkmath.NewInt(amount).Mul(sdkmath.NewInt(int64(math.Pow10(decimal)))) + transferCoin := sdk.NewCoin(tokenPair.Denom, transferIntAmt) + fromChain.MintCoin(sender, transferCoin) + fromChain.Commit() + + packet := suite.IBCITS.TxMakeIbcTransferFromChain2ToChain1(receiver, transferCoin) + + ibcDenom := toChain.QueryDenomHash(packet.GetDestPort(), packet.GetDestChannel(), transferCoin.Denom) + coinBalance := toChain.QueryBalanceByDenom(0, receiver.GetCosmosAddress().String(), ibcDenom) + suite.Require().Equal(transferCoin.Amount.String(), coinBalance.Amount.String()) +} diff --git a/integration_test_util/demo/integration_test_demo_evm_test.go b/integration_test_util/demo/integration_test_demo_evm_test.go new file mode 100644 index 0000000000..a22f1295a5 --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_evm_test.go @@ -0,0 +1,64 @@ +package demo + +import ( + sdkmath "cosmossdk.io/math" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_QC_Evm_Balance() { + balance := suite.queryEvmBalance(0, suite.CITS.WalletAccounts.Number(1).GetEthAddress().String()) + suite.Require().True(balance.GT(sdk.ZeroInt())) +} + +func (suite *DemoTestSuite) Test_QC_Evm_Balance_At_Different_Blocks() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + senderBalanceBefore := suite.queryEvmBalance(0, sender.GetEthAddress().String()) + receiverBalanceBefore := suite.queryEvmBalance(0, receiver.GetEthAddress().String()) + + suite.Require().Truef(senderBalanceBefore.GT(sdk.ZeroInt()), "sender must have balance") + + contextHeightBeforeSend := suite.CITS.CurrentContext.BlockHeight() + suite.Commit() + + _, err := suite.CITS.TxSendViaEVM(sender, receiver, 0.1) + suite.Commit() + suite.Require().NoError(err) + + senderBalanceAfter := suite.queryEvmBalance(0, sender.GetEthAddress().String()) + receiverBalanceAfter := suite.queryEvmBalance(0, receiver.GetEthAddress().String()) + + suite.NotEqualf(senderBalanceBefore.String(), senderBalanceAfter.String(), "sender balance must be reduced") + suite.Truef(senderBalanceAfter.LT(senderBalanceBefore), "sender balance must be reduced") + + suite.NotEqualf(receiverBalanceBefore.String(), receiverBalanceAfter.String(), "receiver balance must be increased") + suite.Truef(receiverBalanceBefore.LT(receiverBalanceAfter), "receiver balance must be increased") + + // Historical block height + historicalSenderBalance := suite.queryEvmBalance(contextHeightBeforeSend, sender.GetEthAddress().String()) + historicalReceiverBalanceAfter := suite.queryEvmBalance(contextHeightBeforeSend, receiver.GetEthAddress().String()) + suite.Equal(senderBalanceBefore.String(), historicalSenderBalance.String(), "mis-match sender balance at historical height") + suite.Equal(receiverBalanceBefore.String(), historicalReceiverBalanceAfter.String(), "mis-match sender balance at historical height") +} + +func (suite *DemoTestSuite) queryEvmBalance(height int64, evmAddress string) sdkmath.Int { + res, err := suite.CITS.QueryClientsAt(height).EVM.Balance( + rpctypes.ContextWithHeight(height), + &evmtypes.QueryBalanceRequest{ + Address: evmAddress, + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + if res.Balance == "0" { + return sdkmath.ZeroInt() + } + bal, ok := sdkmath.NewIntFromString(res.Balance) + suite.Require().True(ok) + return bal +} diff --git a/integration_test_util/demo/integration_test_demo_ibc_test.go b/integration_test_util/demo/integration_test_demo_ibc_test.go new file mode 100644 index 0000000000..e420ab6d30 --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_ibc_test.go @@ -0,0 +1,89 @@ +package demo + +//goland:noinspection SpellCheckingInspection +import ( + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/integration_test_util" + sdk "github.com/cosmos/cosmos-sdk/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + ibcconntypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + "math" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_SetupIbc() { + suite.SetupIbcTest() + suite.testSetupIbc() +} + +func (suite *DemoTestSuite) testSetupIbc() { + suite.SetupIbcTest() + suite.Require().NotNil(suite.IBCITS) + suite.Require().NotNil(suite.IBCITS.Chain1) + suite.Require().NotNil(suite.IBCITS.Chain2) + suite.Require().NotNil(suite.IBCITS.TestChain1) + suite.Require().NotNil(suite.IBCITS.TestChain2) + suite.Require().NotNil(suite.IBCITS.RelayerChain1) + suite.Require().NotNil(suite.IBCITS.RelayerChain2) + + validateChainSetup := func(chain *integration_test_util.ChainIntegrationTestSuite) { + suite.Require().NotNil(chain) + + ibcClientsState := chain.ChainApp.IbcKeeper().ClientKeeper.GetAllClients(chain.CurrentContext) + suite.NotEmptyf(ibcClientsState, "%s must have clients state", chain.ChainConstantsConfig.GetCosmosChainID()) + + resCons, err := chain.ChainApp.IbcKeeper().ConnectionKeeper.Connections(chain.CurrentContext, &ibcconntypes.QueryConnectionsRequest{}) + suite.Require().NoError(err) + suite.NotEmptyf(resCons.Connections, "%s must have connections", chain.ChainConstantsConfig.GetCosmosChainID()) + + channels := chain.ChainApp.IbcKeeper().ChannelKeeper.GetAllChannels(chain.CurrentContext) + if suite.NotEmptyf(channels, "%s must have connections", chain.ChainConstantsConfig.GetCosmosChainID()) { + suite.Equal(channels[0].PortId, "transfer") + suite.Equal(channels[0].ChannelId, "channel-0") + } + } + + chain1 := suite.IBCITS.Chain1 + chain2 := suite.IBCITS.Chain2 + + validateChainSetup(chain1) + validateChainSetup(chain2) +} + +func (suite *DemoTestSuite) Test_Ibc_Transfer() { + suite.SetupIbcTest() + suite.testSetupIbc() + + fromChain, fromTestChain, relayerSourceChain, fromEndpoint := suite.IBCITS.Chain(2) + toChain, _, _, _ := suite.IBCITS.Chain(1) + + transferCoin := fromChain.NewBaseCoin(1) + + sender := relayerSourceChain + receiver := toChain.WalletAccounts.Number(1) + + _ = suite.IBCITS.TxMakeIbcTransfer(fromChain, fromTestChain, fromEndpoint, toChain, sender, receiver, transferCoin) + + denomTraces, err := toChain.ChainApp.IbcTransferKeeper().DenomTraces(toChain.CurrentContext, &ibctransfertypes.QueryDenomTracesRequest{}) + suite.Require().NoError(err) + suite.Require().NotNil(denomTraces) + if suite.Len(denomTraces.DenomTraces, 1) { + denomTrace := denomTraces.DenomTraces[0] + suite.Equal(transferCoin.Denom, denomTrace.BaseDenom) + } + + denomUnit := fromChain.TestConfig.SecondaryDenomUnits[0] + intAmt := sdkmath.NewInt(1).Mul(sdkmath.NewInt(int64(math.Pow10(int(denomUnit.Exponent))))) + transferCoin2 := sdk.NewCoin(denomUnit.Denom, intAmt) + + fromChain.MintCoin(sender, transferCoin2) + suite.IBCITS.CommitAllChains() + + _ = suite.IBCITS.TxMakeIbcTransferFromChain2ToChain1(receiver, transferCoin2) + + denomTraces, err = toChain.ChainApp.IbcTransferKeeper().DenomTraces(toChain.CurrentContext, &ibctransfertypes.QueryDenomTracesRequest{}) + suite.Require().NoError(err) + suite.Require().NotNil(denomTraces) + suite.Len(denomTraces.DenomTraces, 2) +} diff --git a/integration_test_util/demo/integration_test_demo_rpc_test.go b/integration_test_util/demo/integration_test_demo_rpc_test.go new file mode 100644 index 0000000000..59b566728f --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_rpc_test.go @@ -0,0 +1,85 @@ +package demo + +import ( + sdkmath "cosmossdk.io/math" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_QC_Rpc_Balance() { + address := suite.CITS.WalletAccounts.Number(1).GetEthAddress().String() + + res, err := suite.CITS.QueryClients.Rpc.Balance( + rpctypes.ContextWithHeight(0), + &evmtypes.QueryBalanceRequest{ + Address: address, + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + balance, ok := sdkmath.NewIntFromString(res.Balance) + suite.Require().True(ok) + suite.True(balance.GT(sdk.ZeroInt())) + suite.Equal(suite.CITS.TestConfig.InitBalanceAmount, balance) +} + +func (suite *DemoTestSuite) Test_QC_Rpc_Balance_At_Different_Blocks() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + res, err := suite.CITS.QueryClients.Rpc.Balance( + rpctypes.ContextWithHeight(0), + &evmtypes.QueryBalanceRequest{ + Address: sender.GetEthAddress().String(), + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + senderBalanceBefore, _ := sdkmath.NewIntFromString(res.Balance) + + suite.Require().Truef(senderBalanceBefore.GT(sdk.ZeroInt()), "sender must have balance") + + res, err = suite.CITS.QueryClients.Rpc.Balance( + rpctypes.ContextWithHeight(0), + &evmtypes.QueryBalanceRequest{ + Address: receiver.GetEthAddress().String(), + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + receiverBalanceBefore, _ := sdkmath.NewIntFromString(res.Balance) + + err = suite.CITS.TxSend(sender, receiver, 0.1) + suite.Commit() + suite.Require().NoError(err) + + res, err = suite.CITS.QueryClients.Rpc.Balance( + rpctypes.ContextWithHeight(0), + &evmtypes.QueryBalanceRequest{ + Address: sender.GetEthAddress().String(), + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + senderBalanceAfter, _ := sdkmath.NewIntFromString(res.Balance) + + res, err = suite.CITS.QueryClients.Rpc.Balance( + rpctypes.ContextWithHeight(0), + &evmtypes.QueryBalanceRequest{ + Address: receiver.GetEthAddress().String(), + }, + ) + suite.Require().NoError(err) + suite.Require().NotNil(res) + receiverBalanceAfter, _ := sdkmath.NewIntFromString(res.Balance) + + suite.NotEqualf(senderBalanceBefore.String(), senderBalanceAfter.String(), "sender balance must be reduced") + suite.Require().Truef(senderBalanceAfter.LT(senderBalanceBefore), "sender balance must be reduced") + + suite.NotEqualf(receiverBalanceBefore.String(), receiverBalanceAfter.String(), "receiver balance must be increased") + suite.Require().Truef(receiverBalanceBefore.LT(receiverBalanceAfter), "receiver balance must be increased") +} diff --git a/integration_test_util/demo/integration_test_demo_staking_test.go b/integration_test_util/demo/integration_test_demo_staking_test.go new file mode 100644 index 0000000000..7e2619837f --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_staking_test.go @@ -0,0 +1,39 @@ +package demo + +import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +func (suite *DemoTestSuite) Test_TX_Delegate() { + validators := suite.CITS.ChainApp.StakingKeeper().GetAllValidators(suite.Ctx()) + fmt.Println("Validators:", len(validators)) + for _, validator := range validators { + fmt.Println(validator.OperatorAddress) + } + + delegator := suite.CITS.WalletAccounts.Number(1) + validatorAddr := suite.CITS.GetValidatorAddress(1).String() + delegationAmount := suite.CITS.NewBaseCoin(1).Amount + msgDelegate := &stakingtypes.MsgDelegate{ + DelegatorAddress: delegator.GetCosmosAddress().String(), + ValidatorAddress: validatorAddr, + Amount: sdk.Coin{ + Denom: suite.CITS.ChainConstantsConfig.GetMinDenom(), + Amount: delegationAmount, + }, + } + + _, _, err := suite.CITS.DeliverTx(suite.Ctx(), delegator, nil, msgDelegate) + suite.Require().NoError(err) + + suite.Commit() + + delegations, err := suite.CITS.QueryClients.Staking.DelegatorDelegations(suite.Ctx(), &stakingtypes.QueryDelegatorDelegationsRequest{ + DelegatorAddr: delegator.GetCosmosAddress().String(), + }) + suite.Require().NoError(err) + suite.Require().NotNil(delegations) + suite.NotEmpty(delegations.DelegationResponses) +} diff --git a/integration_test_util/demo/integration_test_demo_tendermint_rpc_test.go b/integration_test_util/demo/integration_test_demo_tendermint_rpc_test.go new file mode 100644 index 0000000000..e60718db0e --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_tendermint_rpc_test.go @@ -0,0 +1,19 @@ +package demo + +import ( + "context" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *DemoTestSuite) Test_QC_TmRpc_Block() { + suite.SkipIfDisabledTendermint() + + backupContextHeight := suite.CITS.BaseApp().LastBlockHeight() + suite.Commit() + + resultBlock, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), &backupContextHeight) + suite.Require().NoError(err) + suite.Require().NotNil(resultBlock) + suite.Equal(backupContextHeight, resultBlock.Block.Height) +} diff --git a/integration_test_util/demo/integration_test_demo_test.go b/integration_test_util/demo/integration_test_demo_test.go new file mode 100644 index 0000000000..8fb6d7d427 --- /dev/null +++ b/integration_test_util/demo/integration_test_demo_test.go @@ -0,0 +1,102 @@ +package demo + +//goland:noinspection SpellCheckingInspection +import ( + "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + "testing" +) + +//goland:noinspection GoSnakeCaseUsage,SpellCheckingInspection +type DemoTestSuite struct { + suite.Suite + CITS *integration_test_util.ChainIntegrationTestSuite + IBCITS *integration_test_util.ChainsIbcIntegrationTestSuite +} + +func (suite *DemoTestSuite) App() itutiltypes.ChainApp { + return suite.CITS.ChainApp +} + +func (suite *DemoTestSuite) Ctx() sdk.Context { + return suite.CITS.CurrentContext +} + +func (suite *DemoTestSuite) Commit() { + suite.CITS.Commit() +} + +func TestDemoTestSuite(t *testing.T) { + suite.Run(t, new(DemoTestSuite)) +} + +func (suite *DemoTestSuite) SetupSuite() { +} + +func (suite *DemoTestSuite) SetupTest() { + suite.CITS = integration_test_util.CreateChainIntegrationTestSuite(suite.T(), suite.Require()) +} + +func (suite *DemoTestSuite) SetupIbcTest() { + // There is issue that IBC dual chains not work with Tendermint client so temporary disable it + suite.CITS.Cleanup() // don't use Tendermint enabled chain + + suite.CITS = integration_test_util.CreateChainIntegrationTestSuiteFromChainConfig( + suite.T(), suite.Require(), + integration_test_util.IntegrationTestChain1, + true, + ) + chain2 := integration_test_util.CreateChainIntegrationTestSuiteFromChainConfig( + suite.T(), suite.Require(), + integration_test_util.IntegrationTestChain2, + true, + ) + + suite.IBCITS = integration_test_util.CreateChainsIbcIntegrationTestSuite(suite.CITS, chain2, nil, nil) +} + +func (suite *DemoTestSuite) TearDownTest() { + if suite.IBCITS != nil { + suite.IBCITS.Cleanup() + } else { + suite.CITS.Cleanup() + } +} + +func (suite *DemoTestSuite) TearDownSuite() { +} + +func (suite *DemoTestSuite) SkipIfDisabledTendermint() { + if !suite.CITS.HasTendermint() { + suite.T().Skip("Tendermint is disabled, some methods can not be used, skip") + } +} + +func (suite *DemoTestSuite) TestEnsureStateResetEachTest1() { + suite.testEnsureStateResetEachTest() +} + +func (suite *DemoTestSuite) TestEnsureStateResetEachTest2() { + suite.testEnsureStateResetEachTest() +} + +func (suite *DemoTestSuite) testEnsureStateResetEachTest() { + wallet1 := suite.CITS.WalletAccounts.Number(1) + + balanceBefore := suite.CITS.QueryBalance(0, wallet1.GetCosmosAddress().String()) + suite.Require().Equalf( + suite.CITS.TestConfig.InitBalanceAmount, balanceBefore.Amount, + "balance must be reset to default each test", + ) + suite.True(balanceBefore.Amount.GT(sdk.ZeroInt()), "balance must be reset to default each test") + + // change balance + err := suite.CITS.TxSend(wallet1, suite.CITS.WalletAccounts.Number(2), 0.1) + suite.Commit() + suite.Require().NoError(err) + + balanceAfter := suite.CITS.QueryBalance(0, wallet1.GetCosmosAddress().String()) + suite.Require().Truef(balanceAfter.Amount.LT(balanceBefore.Amount), "balance must be reduced to be evident for next test") +} diff --git a/integration_test_util/ibc_suite.go b/integration_test_util/ibc_suite.go new file mode 100644 index 0000000000..f76b67de80 --- /dev/null +++ b/integration_test_util/ibc_suite.go @@ -0,0 +1,244 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + "crypto/ed25519" + "fmt" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + tmtypes "github.com/cometbft/cometbft/types" + cosmosed25519 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + ibcmock "github.com/cosmos/ibc-go/v7/testing/mock" + "math/big" + "time" +) + +// ChainsIbcIntegrationTestSuite is a wrapper of ChainIntegrationTestSuite for IBC testing. +// Tendermint is Disabled for IBC testing. +type ChainsIbcIntegrationTestSuite struct { + Chain1 *ChainIntegrationTestSuite + Chain2 *ChainIntegrationTestSuite + TestChain1 *ibctesting.TestChain + TestChain2 *ibctesting.TestChain + RelayerChain1 *itutiltypes.TestAccount + RelayerChain2 *itutiltypes.TestAccount + Path *ibctesting.Path + Coordinator *ibctesting.Coordinator +} + +// CreateChainsIbcIntegrationTestSuite initializes an IBC integration test suite from given chains. +// The input chain must disable Tendermint. +func CreateChainsIbcIntegrationTestSuite(chain1, chain2 *ChainIntegrationTestSuite, relayer1, relayer2 *itutiltypes.TestAccount) *ChainsIbcIntegrationTestSuite { + if chain1.HasTendermint() { + panic(fmt.Errorf("chain1 must disable Tendermint")) + } + if chain2.HasTendermint() { + panic(fmt.Errorf("chain2 must disable Tendermint")) + } + + if relayer1 == nil { + relayer1 = NewTestAccount(chain1.t, nil) + chain1.MintCoin(relayer1, chain1.NewBaseCoin(9)) + } + if relayer2 == nil { + relayer2 = NewTestAccount(chain2.t, nil) + chain2.MintCoin(relayer2, chain2.NewBaseCoin(9)) + } + + baseFeeChain1 := chain1.ChainApp.FeeMarketKeeper().GetBaseFee(chain1.CurrentContext) + baseFeeChain2 := chain2.ChainApp.FeeMarketKeeper().GetBaseFee(chain2.CurrentContext) + + chain1.ChainApp.FeeMarketKeeper().SetBaseFee(chain1.CurrentContext, big.NewInt(0)) + chain2.ChainApp.FeeMarketKeeper().SetBaseFee(chain2.CurrentContext, big.NewInt(0)) + + chain1.Commit() + chain2.Commit() + + coordinator := newIbcTestingCoordinator(chain1, chain2, relayer1, relayer2) + + testChain1 := coordinator.GetChain(chain1.ChainConstantsConfig.GetCosmosChainID()) + testChain2 := coordinator.GetChain(chain2.ChainConstantsConfig.GetCosmosChainID()) + + suite := &ChainsIbcIntegrationTestSuite{ + Chain1: chain1, + Chain2: chain2, + TestChain1: testChain1, + TestChain2: testChain2, + RelayerChain1: relayer1, + RelayerChain2: relayer2, + Coordinator: coordinator, + Path: newIbcTransferPath(testChain1, testChain2), + } + + coordinator.CommitNBlocks(testChain1, 2) + coordinator.CommitNBlocks(testChain2, 2) + + coordinator.Setup(suite.Path) + + chain1.CurrentContext = chain1.createNewContext(chain1.CurrentContext, testChain1.CurrentHeader) + chain2.CurrentContext = chain2.createNewContext(chain2.CurrentContext, testChain2.CurrentHeader) + + // restore base fee which was set to 0 for IBC initialization purpose + chain1.ChainApp.FeeMarketKeeper().SetBaseFee(chain1.CurrentContext, baseFeeChain1) + chain2.ChainApp.FeeMarketKeeper().SetBaseFee(chain2.CurrentContext, baseFeeChain2) + + suite.CommitAllChains() // commit fee-market + + suite.Chain1.ibcSuite = suite + suite.Chain2.ibcSuite = suite + + return suite +} + +// newIbcTestingCoordinator creates a new IBC testing coordinator (provided by IBC-go) from given chains. +func newIbcTestingCoordinator(chain1, chain2 *ChainIntegrationTestSuite, relayer1, relayer2 *itutiltypes.TestAccount) *ibctesting.Coordinator { + chains := make(map[string]*ibctesting.TestChain) + coordinator := &ibctesting.Coordinator{ + T: chain1.T(), + CurrentTime: time.Now().UTC(), + } + + ibcTestChain1 := newIbcTestingChain(coordinator, chain1, relayer1) + chains[chain1.ChainConstantsConfig.GetCosmosChainID()] = ibcTestChain1 + + ibcTestChain2 := newIbcTestingChain(coordinator, chain2, relayer2) + chains[chain2.ChainConstantsConfig.GetCosmosChainID()] = ibcTestChain2 + + coordinator.Chains = chains + + return coordinator +} + +// newIbcTestingChain wraps an integration test chain onto an IBC testing chain (provided by IBC-go). +func newIbcTestingChain(coordinator *ibctesting.Coordinator, chain *ChainIntegrationTestSuite, relayer *itutiltypes.TestAccount) *ibctesting.TestChain { + chainId := chain.ChainConstantsConfig.GetCosmosChainID() + testApp := chain.ChainApp.IbcTestingApp() + resRelayerAcc, err := chain.QueryClients.Auth.Account(chain.CurrentContext, &authtypes.QueryAccountRequest{ + Address: relayer.GetCosmosAddress().String(), + }) + chain.Require().NoError(err) + chain.Require().NotNil(resRelayerAcc) + var relayerAcc authtypes.AccountI + err2 := chain.EncodingConfig.Codec.UnpackAny(resRelayerAcc.Account, &relayerAcc) + chain.Require().NoError(err2) + + signers := make(map[string]tmtypes.PrivValidator) + for _, validatorAccount := range chain.ValidatorAccounts { + //goland:noinspection GoDeprecation + pv := ibcmock.PV{ + PrivKey: &cosmosed25519.PrivKey{ + Key: ed25519.NewKeyFromSeed(validatorAccount.PrivateKey.Key), + }, + } + pubKey, err := pv.GetPubKey() + chain.Require().NoError(err) + signers[pubKey.Address().String()] = pv + } + + return &ibctesting.TestChain{ + T: chain.t, + Coordinator: coordinator, + ChainID: chainId, + App: testApp, + CurrentHeader: chain.CurrentContext.BlockHeader(), + QueryServer: chain.ChainApp.IbcKeeper(), + TxConfig: chain.EncodingConfig.TxConfig, + Codec: chain.EncodingConfig.Codec, + Vals: chain.ValidatorSet, + Signers: signers, + SenderPrivKey: relayer.PrivateKey, + SenderAccount: relayerAcc, + NextVals: chain.ValidatorSet, + } +} + +// newIbcTransferPath creates a new IBC path for transfer purpose. +func newIbcTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort + path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + path.EndpointA.ChannelConfig.Version = ibctransfertypes.Version + path.EndpointB.ChannelConfig.Version = ibctransfertypes.Version + + return path +} + +// CommitAllChains is a MUST call method when IBC testing, it performs commit on all inner chains. +// Due to the complicated logic of the chain test suite, this function is required to sync required data for all chains. +func (suite *ChainsIbcIntegrationTestSuite) CommitAllChains() { + chain1, testChain1, _, _ := suite.Chain(1) + chain2, testChain2, _, _ := suite.Chain(2) + + suite.Coordinator.CurrentTime = chain1.CurrentContext.BlockHeader().Time + + headerChain1 := testChain1.CurrentHeader + headerChain1.Time = suite.Coordinator.CurrentTime // sync + headerChain2 := testChain2.CurrentHeader + headerChain2.Time = suite.Coordinator.CurrentTime // sync + + chain1.CurrentContext = chain1.createNewContext(chain1.CurrentContext, headerChain1) + chain2.CurrentContext = chain2.createNewContext(chain2.CurrentContext, headerChain2) + + chain1.ibcSuiteCommit() + chain2.ibcSuiteCommit() + + testChain1.CurrentHeader = chain1.CurrentContext.BlockHeader() + testChain2.CurrentHeader = chain2.CurrentContext.BlockHeader() + + suite.Coordinator.CurrentTime = chain1.CurrentContext.BlockHeader().Time // sync +} + +// TemporarySetBaseFeeZero is a helper function that used to bypass EIP-1559 by x/feemarket module. +// +// It does temporarily set the base fee of all chains to 0 and returns a function that is used to restore the original base fee amount. +func (suite *ChainsIbcIntegrationTestSuite) TemporarySetBaseFeeZero() (releaser func()) { + chain1 := suite.Chain1 + chain2 := suite.Chain2 + + baseFeeChain1 := chain1.ChainApp.FeeMarketKeeper().GetBaseFee(chain1.CurrentContext) + baseFeeChain2 := chain2.ChainApp.FeeMarketKeeper().GetBaseFee(chain2.CurrentContext) + + chain1.ChainApp.FeeMarketKeeper().SetBaseFee(chain1.CurrentContext, big.NewInt(0)) + chain2.ChainApp.FeeMarketKeeper().SetBaseFee(chain2.CurrentContext, big.NewInt(0)) + + suite.CommitAllChains() + + return func() { + chain1.CurrentContext = chain1.createNewContext(chain1.CurrentContext, suite.TestChain1.CurrentHeader) + chain2.CurrentContext = chain2.createNewContext(chain2.CurrentContext, suite.TestChain2.CurrentHeader) + + // restore base fee which was set to 0 for IBC initialization purpose + chain1.ChainApp.FeeMarketKeeper().SetBaseFee(chain1.CurrentContext, baseFeeChain1) + chain2.ChainApp.FeeMarketKeeper().SetBaseFee(chain2.CurrentContext, baseFeeChain2) + + suite.CommitAllChains() // commit fee-market + } +} + +// Chain returns the chain suite, test chain, relayer and endpoint of given chain by number. +func (suite *ChainsIbcIntegrationTestSuite) Chain(number int) ( + chainSuite *ChainIntegrationTestSuite, + testChain *ibctesting.TestChain, + relayer *itutiltypes.TestAccount, + endpoint *ibctesting.Endpoint, +) { + if number == 1 { + return suite.Chain1, suite.TestChain1, suite.RelayerChain1, suite.Path.EndpointA + } + if number == 2 { + return suite.Chain2, suite.TestChain2, suite.RelayerChain2, suite.Path.EndpointB + } + panic(fmt.Errorf("not supported chain %d", number)) +} + +// Cleanup performs cleanup tasks on each of the inner chains. +func (suite *ChainsIbcIntegrationTestSuite) Cleanup() { + if suite == nil { + return + } + + suite.Chain1.Cleanup() + suite.Chain2.Cleanup() +} diff --git a/integration_test_util/ibc_suite_tx.go b/integration_test_util/ibc_suite_tx.go new file mode 100644 index 0000000000..5d82e674ee --- /dev/null +++ b/integration_test_util/ibc_suite_tx.go @@ -0,0 +1,46 @@ +package integration_test_util + +//goland:noinspection SpellCheckingInspection +import ( + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" +) + +// TxMakeIbcTransfer creates and submit an IBC transfer from given chain to another. +// The relayed packet will be returned. +func (suite *ChainsIbcIntegrationTestSuite) TxMakeIbcTransfer(fromChain *ChainIntegrationTestSuite, fromTestChain *ibctesting.TestChain, fromEndpoint *ibctesting.Endpoint, toChain *ChainIntegrationTestSuite, sender, receiver *itutiltypes.TestAccount, transferCoin sdk.Coin) channeltypes.Packet { + timeoutHeight := toChain.GetIbcTimeoutHeight(100) + + msgTransfer := ibctransfertypes.NewMsgTransfer(fromEndpoint.ChannelConfig.PortID, fromEndpoint.ChannelID, transferCoin, sender.GetCosmosAddress().String(), receiver.GetCosmosAddress().String(), timeoutHeight, 0, "") + + releaser := suite.TemporarySetBaseFeeZero() + + res, err := fromTestChain.SendMsgs(msgTransfer) + fromChain.Require().NoError(err) + + packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) + fromChain.Require().NoError(err) + + err = suite.Path.RelayPacket(packet) + toChain.Require().NoError(err) + + releaser() + + suite.CommitAllChains() + + return packet +} + +// TxMakeIbcTransferFromChain2ToChain1 creates and submit an IBC transfer from chain2 to chain1. +// The relayed packet will be returned. +func (suite *ChainsIbcIntegrationTestSuite) TxMakeIbcTransferFromChain2ToChain1(receiver *itutiltypes.TestAccount, transferCoin sdk.Coin) channeltypes.Packet { + fromChain, fromTestChain, relayerSourceChain, fromEndpoint := suite.Chain(2) + toChain, _, _, _ := suite.Chain(1) + + sender := relayerSourceChain + + return suite.TxMakeIbcTransfer(fromChain, fromTestChain, fromEndpoint, toChain, sender, receiver, transferCoin) +} diff --git a/integration_test_util/query_server_test_helper.go b/integration_test_util/query_server_test_helper.go new file mode 100644 index 0000000000..c60cb1c84f --- /dev/null +++ b/integration_test_util/query_server_test_helper.go @@ -0,0 +1,88 @@ +package integration_test_util + +import ( + gocontext "context" + "fmt" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + "google.golang.org/grpc/encoding" + "google.golang.org/grpc/metadata" + + abci "github.com/cometbft/cometbft/abci/types" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" + + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// QueryServiceTestHelper provides a helper for making grpc query service +// rpc calls in unit tests. It implements both the grpc Server and ClientConn +// interfaces needed to register a query service server and create a query +// service client. +type QueryServiceTestHelper struct { + *baseapp.GRPCQueryRouter + Ctx sdk.Context + cdc encoding.Codec +} + +var ( + _ gogogrpc.Server = &QueryServiceTestHelper{} + _ gogogrpc.ClientConn = &QueryServiceTestHelper{} +) + +// NewQueryServerTestHelper creates a new QueryServiceTestHelper that wraps +// the provided sdk.Context. +// +// This one is copied from baseapp of cosmos-sdk to add ability to include x-cosmos-block-height header +func NewQueryServerTestHelper(ctx sdk.Context, interfaceRegistry types.InterfaceRegistry) *QueryServiceTestHelper { + qrt := baseapp.NewGRPCQueryRouter() + qrt.SetInterfaceRegistry(interfaceRegistry) + return &QueryServiceTestHelper{GRPCQueryRouter: qrt, Ctx: ctx, cdc: codec.NewProtoCodec(interfaceRegistry).GRPCCodec()} +} + +// Invoke implements the grpc ClientConn.Invoke method +func (q *QueryServiceTestHelper) Invoke(_ gocontext.Context, method string, args, reply interface{}, callOptions ...grpc.CallOption) error { + querier := q.Route(method) + if querier == nil { + return fmt.Errorf("handler not found for %s", method) + } + reqBz, err := q.cdc.Marshal(args) + if err != nil { + return err + } + + for _, option := range callOptions { + if option == nil { + continue + } + + if header, ok := option.(grpc.HeaderCallOption); ok { + if header.HeaderAddr != nil { + var mdI = metadata.New(map[string]string{ + grpctypes.GRPCBlockHeightHeader: fmt.Sprintf("%d", q.Ctx.BlockHeight()), + }) + *header.HeaderAddr = mdI + } + break + } + } + + res, err := querier(q.Ctx, abci.RequestQuery{Data: reqBz}) + if err != nil { + return err + } + + err = q.cdc.Unmarshal(res.Value, reply) + if err != nil { + return err + } + + return nil +} + +// NewStream implements the grpc ClientConn.NewStream method +func (q *QueryServiceTestHelper) NewStream(gocontext.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, fmt.Errorf("not supported") +} diff --git a/integration_test_util/reactor.go b/integration_test_util/reactor.go new file mode 100644 index 0000000000..f5586dbe48 --- /dev/null +++ b/integration_test_util/reactor.go @@ -0,0 +1,34 @@ +package integration_test_util + +import ( + "github.com/EscanBE/evermint/v12/constants" + sdk "github.com/cosmos/cosmos-sdk/types" + + // Force-load the tracer engines to trigger registration due to Go-Ethereum v1.10.15 changes + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + _ "github.com/ethereum/go-ethereum/eth/tracers/native" +) + +func init() { + //goland:noinspection SpellCheckingInspection + const prefix = constants.Bech32Prefix + + config := sdk.GetConfig() + + config.SetBech32PrefixForAccount( + prefix, + prefix+sdk.PrefixPublic, + ) + + config.SetBech32PrefixForValidator( + prefix+sdk.PrefixValidator+sdk.PrefixOperator, + prefix+sdk.PrefixValidator+sdk.PrefixOperator+sdk.PrefixPublic, + ) + + config.SetBech32PrefixForConsensusNode( + prefix+sdk.PrefixValidator+sdk.PrefixConsensus, + prefix+sdk.PrefixValidator+sdk.PrefixConsensus+sdk.PrefixPublic, + ) + + sdk.DefaultBondDenom = constants.BaseDenom +} diff --git a/integration_test_util/types/accounts.go b/integration_test_util/types/accounts.go new file mode 100644 index 0000000000..33e727188e --- /dev/null +++ b/integration_test_util/types/accounts.go @@ -0,0 +1,88 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + "crypto/ed25519" + "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" + tmcrypto "github.com/cometbft/cometbft/crypto" + tmed25519 "github.com/cometbft/cometbft/crypto/ed25519" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cosmosed25519 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type TestAccount struct { + PrivateKey *ethsecp256k1.PrivKey + Signer keyring.Signer + Type TestAccountType +} + +type TestAccountType int8 + +const ( + TestAccountTypeValidator TestAccountType = iota + TestAccountTypeWallet +) + +func (a TestAccount) GetPubKey() cryptotypes.PubKey { + return a.PrivateKey.PubKey() +} + +func (a TestAccount) GetSdkPubKey() cryptotypes.PubKey { + pk, err := cryptocodec.FromTmPubKeyInterface(a.GetTmPubKey()) + if err != nil { + panic(err) + } + return pk +} + +func (a TestAccount) GetTmPubKey() tmcrypto.PubKey { + return a.GetTmPrivKey().PubKey() +} + +func (a TestAccount) GetTmPrivKey() tmcrypto.PrivKey { + //goland:noinspection GoDeprecation + pv := mock.PV{ + PrivKey: &cosmosed25519.PrivKey{ + Key: ed25519.NewKeyFromSeed(a.PrivateKey.Key), + }, + } + + var tmPrivEd25519 tmed25519.PrivKey + tmPrivEd25519 = pv.PrivKey.Bytes() + + return tmPrivEd25519 +} + +// GetValidatorAddress returns validator address of the account, deliver from sdk pubkey. +// Should use suite.GetValidatorAddress() instead for correcting with Tendermint node mode. +func (a TestAccount) GetValidatorAddress() sdk.ValAddress { + return sdk.ValAddress(a.GetPubKey().Address()) +} + +func (a TestAccount) GetConsensusAddress() sdk.ConsAddress { + return sdk.ConsAddress(a.GetSdkPubKey().Address()) +} + +func (a TestAccount) GetCosmosAddress() sdk.AccAddress { + return sdk.AccAddress(a.GetPubKey().Address()) +} + +func (a TestAccount) GetEthAddress() common.Address { + return common.BytesToAddress(a.GetPubKey().Address().Bytes()) +} + +func (a TestAccount) ComputeContractAddress(nonce uint64) common.Address { + return crypto.CreateAddress(a.GetEthAddress(), nonce) +} + +type TestAccounts []*TestAccount + +func (a TestAccounts) Number(num int) *TestAccount { + return a[num-1] +} diff --git a/integration_test_util/types/chain_app.go b/integration_test_util/types/chain_app.go new file mode 100644 index 0000000000..a26268393d --- /dev/null +++ b/integration_test_util/types/chain_app.go @@ -0,0 +1,46 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + erc20keeper "github.com/EscanBE/evermint/v12/x/erc20/keeper" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + feemarketkeeper "github.com/EscanBE/evermint/v12/x/feemarket/keeper" + ibctransferkeeper "github.com/EscanBE/evermint/v12/x/ibc/transfer/keeper" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/baseapp" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" + ibctesting "github.com/cosmos/ibc-go/v7/testing" +) + +type ChainApp interface { + App() abci.Application + BaseApp() *baseapp.BaseApp + IbcTestingApp() ibctesting.TestingApp + InterfaceRegistry() codectypes.InterfaceRegistry + + // Keepers + + AccountKeeper() *authkeeper.AccountKeeper + BankKeeper() bankkeeper.Keeper + DistributionKeeper() distkeeper.Keeper + Erc20Keeper() *erc20keeper.Keeper + EvmKeeper() *evmkeeper.Keeper + FeeMarketKeeper() *feemarketkeeper.Keeper + GovKeeper() *govkeeper.Keeper + IbcTransferKeeper() *ibctransferkeeper.Keeper + IbcKeeper() *ibckeeper.Keeper + SlashingKeeper() *slashingkeeper.Keeper + StakingKeeper() *stakingkeeper.Keeper + + // Tx + + FundAccount(ctx sdk.Context, account *TestAccount, amounts sdk.Coins) error +} diff --git a/integration_test_util/types/chain_app_imp.go b/integration_test_util/types/chain_app_imp.go new file mode 100644 index 0000000000..3fadf035ca --- /dev/null +++ b/integration_test_util/types/chain_app_imp.go @@ -0,0 +1,42 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + chainapp "github.com/EscanBE/evermint/v12/app" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/baseapp" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" +) + +var _ ChainApp = &chainAppImp{} + +type chainAppImp struct { + app *chainapp.Evermint +} + +func (c chainAppImp) App() abci.Application { + return c.app +} + +func (c chainAppImp) BaseApp() *baseapp.BaseApp { + return c.app.BaseApp +} + +func (c chainAppImp) IbcTestingApp() ibctesting.TestingApp { + return c.app +} + +func (c chainAppImp) InterfaceRegistry() codectypes.InterfaceRegistry { + return c.app.InterfaceRegistry() +} + +func (c chainAppImp) FundAccount(ctx sdk.Context, account *TestAccount, amounts sdk.Coins) error { + if err := c.BankKeeper().MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { + return err + } + + return c.BankKeeper().SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, account.GetCosmosAddress(), amounts) +} diff --git a/integration_test_util/types/chain_app_imp_init.go b/integration_test_util/types/chain_app_imp_init.go new file mode 100644 index 0000000000..ed1004574a --- /dev/null +++ b/integration_test_util/types/chain_app_imp_init.go @@ -0,0 +1,363 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + sdkmath "cosmossdk.io/math" + "cosmossdk.io/simapp" + "cosmossdk.io/simapp/params" + "crypto/ed25519" + "encoding/json" + "fmt" + chainapp "github.com/EscanBE/evermint/v12/app" + "github.com/EscanBE/evermint/v12/constants" + itutilutils "github.com/EscanBE/evermint/v12/integration_test_util/utils" + etherminttypes "github.com/EscanBE/evermint/v12/types" + erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/log" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cosmosed25519 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/testutil/mock" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + "strings" + "time" +) + +var defaultConsensusParams = &tmtypes.ConsensusParams{ + Block: tmtypes.BlockParams{ + MaxBytes: 200000, + MaxGas: 40000000, // 40m + }, + Evidence: tmtypes.EvidenceParams{ + MaxAgeNumBlocks: 302400, + MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration + MaxBytes: 10000, + }, + Validator: tmtypes.ValidatorParams{ + PubKeyTypes: []string{ + tmtypes.ABCIPubKeyTypeEd25519, + }, + }, +} + +const TendermintGovVotingPeriod = 5 * time.Second + +func NewChainApp(chainCfg ChainConfig, disableTendermint bool, testConfig TestConfig, encCfg params.EncodingConfig, db *MemDB, validatorAccounts TestAccounts, walletAccounts TestAccounts, genesisAccountBalance sdk.Coins, tempHolder *TemporaryHolder, logger log.Logger) (chainApp ChainApp, tendermintApp TendermintApp, validatorSet *tmtypes.ValidatorSet) { + defaultNodeHome := chainapp.DefaultNodeHome + moduleBasics := chainapp.ModuleBasics + + // create validator set + var validators []*tmtypes.Validator + for _, validatorAccount := range validatorAccounts { + //goland:noinspection GoDeprecation + pv := mock.PV{ + PrivKey: &cosmosed25519.PrivKey{ + Key: ed25519.NewKeyFromSeed(validatorAccount.PrivateKey.Key), + }, + } + pubKey, err := pv.GetPubKey() + if err != nil { + panic(err) + } + validators = append(validators, tmtypes.NewValidator(pubKey, 1)) + } + valSet := tmtypes.NewValidatorSet(validators) + + // generate genesis accounts + var genesisValidatorAccounts []authtypes.GenesisAccount + var genesisWalletAccounts []authtypes.GenesisAccount + var genesisBalances []banktypes.Balance + var signingInfos []slashingtypes.SigningInfo + for i, account := range append(validatorAccounts, walletAccounts...) { + acc := ðerminttypes.EthAccount{ + BaseAccount: authtypes.NewBaseAccount(account.GetCosmosAddress(), account.GetPubKey(), uint64(i), 0), + CodeHash: common.BytesToHash(evmtypes.EmptyCodeHash).Hex(), + } + if account.Type == TestAccountTypeValidator { + genesisValidatorAccounts = append(genesisValidatorAccounts, acc) + + signingInfos = append(signingInfos, slashingtypes.SigningInfo{ + Address: account.GetConsensusAddress().String(), + ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{ + Address: account.GetConsensusAddress().String(), + StartHeight: 0, + IndexOffset: 0, + JailedUntil: time.Time{}, + Tombstoned: false, + MissedBlocksCounter: 0, + }, + }) + } else if account.Type == TestAccountTypeWallet { + genesisWalletAccounts = append(genesisWalletAccounts, acc) + } else { + panic(fmt.Sprintf("unknown account type %d", account.Type)) + } + genesisBalances = append(genesisBalances, banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: genesisAccountBalance, + }) + } + + app := chainapp.NewEvermint( + logger, // logger + db, // db + nil, // trace store + true, // load latest + map[int64]bool{}, // skipUpgradeHeights + defaultNodeHome, // homePath + 0, // invCheckPeriod + encCfg, // encodingConfig + simtestutil.NewAppOptionsWithFlagHome(defaultNodeHome), // appOpts + baseapp.SetChainID(chainCfg.CosmosChainId), // baseAppOptions + ) + + // init chain must be called to stop deliverState from being nil + genesisState := moduleBasics.DefaultGenesis(encCfg.Codec) + + genesisState = genesisStateWithValSet(chainCfg, disableTendermint, testConfig, encCfg.Codec, genesisState, valSet, genesisValidatorAccounts, genesisWalletAccounts, genesisBalances, signingInfos) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + if err != nil { + panic(err) + } + + cai := &chainAppImp{ + app: app, + } + + genesisDoc := tmtypes.GenesisDoc{ + GenesisTime: time.Time{}, + ChainID: chainCfg.CosmosChainId, + InitialHeight: 0, + ConsensusParams: defaultConsensusParams, + Validators: make([]tmtypes.GenesisValidator, len(valSet.Validators)), + AppHash: nil, + AppState: stateBytes, + } + + for i, validator := range valSet.Validators { + genesisDoc.Validators[i] = tmtypes.GenesisValidator{ + Address: validator.Address, + PubKey: validator.PubKey, + Power: validator.VotingPower, + Name: "", + } + } + tempHolder.CacheGenesisDoc(&genesisDoc) + + if disableTendermint { + consensusParams := defaultConsensusParams.ToProto() + app.InitChain(abci.RequestInitChain{ + ChainId: chainCfg.CosmosChainId, + ConsensusParams: &consensusParams, + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + InitialHeight: 0, + }) + tendermintApp = nil + } else { + validator := validatorAccounts.Number(1) + if validator.GetValidatorAddress().String() != sdk.ValAddress(validator.GetPubKey().Address()).String() { + panic("validator address does not match") + } + node, rpcPort, tempFiles := itutilutils.StartTendermintNode(app, &genesisDoc, db, validator.GetTmPrivKey(), logger) + for _, tempFile := range tempFiles { + tempHolder.AddTempFile(tempFile) + } + tendermintApp = NewTendermintApp(node, rpcPort) + } + + return cai, tendermintApp, valSet +} + +func genesisStateWithValSet(chainCfg ChainConfig, disableTendermint bool, testConfig TestConfig, codec codec.Codec, genesisState simapp.GenesisState, valSet *tmtypes.ValidatorSet, genesisValidatorAccounts []authtypes.GenesisAccount, genesisWalletAccounts []authtypes.GenesisAccount, balances []banktypes.Balance, signingInfos []slashingtypes.SigningInfo) simapp.GenesisState { + genesisAccounts := append(genesisValidatorAccounts, genesisWalletAccounts...) + + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genesisAccounts) + genesisState[authtypes.ModuleName] = codec.MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.DefaultPowerReduction + + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens to total supply + totalSupply = totalSupply.Add(b.Coins...) + } + + for i, val := range valSet.Validators { + pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) + if err != nil { + panic(err) + } + pkAny, err := codectypes.NewAnyWithValue(pk) + if err != nil { + panic(err) + } + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: sdk.OneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + MinSelfDelegation: sdk.OneInt(), + } + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genesisValidatorAccounts[i].GetAddress(), val.Address.Bytes(), sdk.OneDec())) + + totalSupply = totalSupply.Add(sdk.NewCoin(chainCfg.BaseDenom, bondAmt)) + } + + // set validators and delegations + stakingParams := stakingtypes.DefaultParams() + stakingParams.BondDenom = chainCfg.BaseDenom + stakingGenesis := stakingtypes.NewGenesisState(stakingParams, validators, delegations) + genesisState[stakingtypes.ModuleName] = codec.MustMarshalJSON(stakingGenesis) + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(chainCfg.BaseDenom, bondAmt.MulRaw(int64(len(validators))))}, + }) + + // update total supply + baseDenomDisplay := strings.ToUpper(chainCfg.BaseDenom[1:]) + denomMetadata := []banktypes.Metadata{ + { + Description: "Base denom metadata", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: chainCfg.BaseDenom, + Exponent: 0, + }, + { + Denom: baseDenomDisplay, + Exponent: constants.BaseDenomExponent, + }, + }, + Base: chainCfg.BaseDenom, + Display: baseDenomDisplay, + Name: baseDenomDisplay, + Symbol: baseDenomDisplay, + }, + } + for _, secondaryDenomUnit := range testConfig.SecondaryDenomUnits { + secondDenomDisplay := strings.ToUpper(secondaryDenomUnit.Denom[1:]) + denomMetadata = append(denomMetadata, banktypes.Metadata{ + Description: "Second denom metadata", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: secondaryDenomUnit.Denom, + Exponent: 0, + }, + { + Denom: secondDenomDisplay, + Exponent: secondaryDenomUnit.Exponent, + }, + }, + Base: secondaryDenomUnit.Denom, + Display: secondDenomDisplay, + Name: secondDenomDisplay, + Symbol: secondDenomDisplay, + }, + ) + } + + { + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, denomMetadata, []banktypes.SendEnabled{}) + genesisState[banktypes.ModuleName] = codec.MustMarshalJSON(bankGenesis) + } + + { + // x/feemarket + feeMarketGenesis := feemarkettypes.DefaultGenesisState() + if feeMarketGenesis != nil { + genesisState[feemarkettypes.ModuleName] = codec.MustMarshalJSON(feeMarketGenesis) + } + } + + { + // x/evm + var evmGenesis *evmtypes.GenesisState + evmGenesis = evmtypes.DefaultGenesisState() + if evmGenesis != nil { + evmGenesis.Params.EvmDenom = chainCfg.BaseDenom + genesisState[evmtypes.ModuleName] = codec.MustMarshalJSON(evmGenesis) + } + } + + { + // x/gov + var govGenesis *govv1types.GenesisState + govGenesis = govv1types.DefaultGenesisState() + if govGenesis != nil { + govGenesis.Params.MinDeposit[0].Denom = chainCfg.BaseDenom + govGenesis.Params.MinDeposit[0].Amount = sdkmath.NewIntFromUint64(2) + var votingPeriod time.Duration + if disableTendermint { + votingPeriod = 30 * time.Minute + } else { + // due to tendermint block time not configurable time jumping, we need to set a low voting period + votingPeriod = TendermintGovVotingPeriod + } + govGenesis.Params.VotingPeriod = &votingPeriod + genesisState[govtypes.ModuleName] = codec.MustMarshalJSON(govGenesis) + } + } + + { + // x/mint + var mintGenesis *minttypes.GenesisState + mintGenesis = minttypes.DefaultGenesisState() + if mintGenesis != nil { + mintGenesis.Params.MintDenom = chainCfg.BaseDenom + genesisState[minttypes.ModuleName] = codec.MustMarshalJSON(mintGenesis) + } + } + + { + // x/erc20 + var erc20Genesis *erc20types.GenesisState + erc20Genesis = erc20types.DefaultGenesisState() + if erc20Genesis != nil { + erc20Genesis.Params.EnableErc20 = true + erc20Genesis.Params.EnableEVMHook = true + genesisState[erc20types.ModuleName] = codec.MustMarshalJSON(erc20Genesis) + } + } + + { + // x/slashing + var slashingGenesis *slashingtypes.GenesisState + slashingGenesis = slashingtypes.DefaultGenesisState() + if slashingGenesis != nil { + slashingGenesis.SigningInfos = signingInfos + genesisState[slashingtypes.ModuleName] = codec.MustMarshalJSON(slashingGenesis) + } + } + + return genesisState +} diff --git a/integration_test_util/types/chain_app_imp_keepers.go b/integration_test_util/types/chain_app_imp_keepers.go new file mode 100644 index 0000000000..f52acde5f2 --- /dev/null +++ b/integration_test_util/types/chain_app_imp_keepers.go @@ -0,0 +1,60 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + erc20keeper "github.com/EscanBE/evermint/v12/x/erc20/keeper" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + feemarketkeeper "github.com/EscanBE/evermint/v12/x/feemarket/keeper" + ibctransferkeeper "github.com/EscanBE/evermint/v12/x/ibc/transfer/keeper" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" +) + +func (c chainAppImp) AccountKeeper() *authkeeper.AccountKeeper { + return &c.app.AccountKeeper +} + +func (c chainAppImp) BankKeeper() bankkeeper.Keeper { + return c.app.BankKeeper +} + +func (c chainAppImp) DistributionKeeper() distkeeper.Keeper { + return c.app.DistrKeeper +} + +func (c chainAppImp) Erc20Keeper() *erc20keeper.Keeper { + return &c.app.Erc20Keeper +} + +func (c chainAppImp) EvmKeeper() *evmkeeper.Keeper { + return c.app.EvmKeeper +} + +func (c chainAppImp) FeeMarketKeeper() *feemarketkeeper.Keeper { + return &c.app.FeeMarketKeeper +} + +func (c chainAppImp) GovKeeper() *govkeeper.Keeper { + return &c.app.GovKeeper +} + +func (c chainAppImp) IbcTransferKeeper() *ibctransferkeeper.Keeper { + return &c.app.TransferKeeper +} + +func (c chainAppImp) IbcKeeper() *ibckeeper.Keeper { + return c.app.IBCKeeper +} + +func (c chainAppImp) SlashingKeeper() *slashingkeeper.Keeper { + return &c.app.SlashingKeeper +} + +func (c chainAppImp) StakingKeeper() *stakingkeeper.Keeper { + return c.app.StakingKeeper +} diff --git a/integration_test_util/types/chain_config.go b/integration_test_util/types/chain_config.go new file mode 100644 index 0000000000..0b4432b3a1 --- /dev/null +++ b/integration_test_util/types/chain_config.go @@ -0,0 +1,49 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + sdkmath "cosmossdk.io/math" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "math/big" +) + +type ChainConfig struct { + CosmosChainId string + BaseDenom string + Bech32Prefix string + EvmChainId int64 + EvmChainIdBigInt *big.Int // dynamic: calculated from EvmChainId +} + +type TestConfig struct { + SecondaryDenomUnits []banktypes.DenomUnit + InitBalanceAmount sdkmath.Int + DefaultFeeAmount sdkmath.Int + DisableTendermint bool +} + +type ChainConstantConfig struct { + cosmosChainId string + minDenom string + baseExponent int +} + +func NewChainConstantConfig(cosmosChainId, minDenom string, baseExponent int) ChainConstantConfig { + return ChainConstantConfig{ + cosmosChainId: cosmosChainId, + minDenom: minDenom, + baseExponent: baseExponent, + } +} + +func (c ChainConstantConfig) GetCosmosChainID() string { + return c.cosmosChainId +} + +func (c ChainConstantConfig) GetMinDenom() string { + return c.minDenom +} + +func (c ChainConstantConfig) GetBaseExponent() int { + return c.baseExponent +} diff --git a/integration_test_util/types/db/block.go b/integration_test_util/types/db/block.go new file mode 100644 index 0000000000..77cdd719ac --- /dev/null +++ b/integration_test_util/types/db/block.go @@ -0,0 +1,32 @@ +package db + +import "database/sql" + +type BlockRecord struct { + Height int64 + Hash string + NumTxs int16 + NumEtx int16 + NumEtxCreateContract int16 + ProposerAddress sql.NullString + Epoch int64 + ParentHash string + StateRoot string + Size int + TotalValueH int64 + TotalValueL int64 + GasUsed int64 + GasLimit int64 + BaseFee int64 + CoinChanges sql.NullString + Erc20InvolvedAddresses []string + Erc721InvolvedAddresses []string + Erc1155InvolvedAddresses []string + JustInvolvedAddresses []string + AnyBalanceError sql.NullBool + Extra sql.NullString + EsMode sql.NullBool + DataVersion int16 + OutDated bool + BurntFee int64 +} diff --git a/integration_test_util/types/db/evm_internal_tx.go b/integration_test_util/types/db/evm_internal_tx.go new file mode 100644 index 0000000000..d65f6d32bf --- /dev/null +++ b/integration_test_util/types/db/evm_internal_tx.go @@ -0,0 +1,19 @@ +package db + +type EvmInternalTxRecord struct { + TransactionHash string + EvmHash string + Height int64 + TransactionIndex int16 + MessageIndex int16 + NestedMessageIndex int16 + Identifier string + Type string + From string + To string + ValueH int64 + ValueL int64 + Gas int32 + Order int16 + PartitionId int64 +} diff --git a/integration_test_util/types/db/evm_log.go b/integration_test_util/types/db/evm_log.go new file mode 100644 index 0000000000..d5940c7009 --- /dev/null +++ b/integration_test_util/types/db/evm_log.go @@ -0,0 +1,23 @@ +package db + +import "database/sql" + +type EvmLogRecord struct { + TransactionHash string + EvmHash string + Height int64 + TransactionIndex int16 + MessageIndex int16 + NestedMessageIndex int16 + LogIndex int16 + Emitter string + Topic0 sql.NullString + Topic1 sql.NullString + Topic2 sql.NullString + Topic3 sql.NullString + Data sql.NullString + NftTokenIds []string + ValidErcTransfer sql.NullInt16 + Removed sql.NullBool + PartitionId int64 +} diff --git a/integration_test_util/types/db/message.go b/integration_test_util/types/db/message.go new file mode 100644 index 0000000000..3f1be41dcf --- /dev/null +++ b/integration_test_util/types/db/message.go @@ -0,0 +1,41 @@ +package db + +import "database/sql" + +type MessageRecord struct { + TransactionHash string + Index int16 + Type string + InvolvedAccountAddrs []string + PartitionId int64 + Height int64 + Content string + TransactionIndex int16 + NestedIndex int16 + NestedType sql.NullString + NumNested int16 + InnerContent sql.NullString + IbcDenom sql.NullString + EvmTxHash sql.NullString + EvmFrom sql.NullString + EvmTo sql.NullString + EvmNonce sql.NullInt64 + EvmTxType sql.NullInt16 + EvmCallType sql.NullString + EvmInputData sql.NullString + EvmLogBloom sql.NullString + EvmValueH int64 + EvmValueL int64 + EvmGasPrice string + EvmBurntFee int64 + EvmInternalTxBalanceAdjustment sql.NullString + EvmSmartContracts []string + EvmInternalTxs sql.NullInt16 + EvmFailedReason sql.NullString + EvmMethod sql.NullString + EvmRevenueAddr sql.NullString + SortRef string + TotalValueH int64 + TotalValueL int64 + Success bool +} diff --git a/integration_test_util/types/db/message_ibc.go b/integration_test_util/types/db/message_ibc.go new file mode 100644 index 0000000000..85ff5b904b --- /dev/null +++ b/integration_test_util/types/db/message_ibc.go @@ -0,0 +1,22 @@ +package db + +import "database/sql" + +type MessageIbcRecord struct { + Height int64 + TransactionIndex int16 + MessageIndex int16 + NestedMessageIndex int16 + TransactionHash string + SequenceNo string + Port string + Channel string + CounterPartyChainId sql.NullString + CounterPartyPort sql.NullString + CounterPartyChannel sql.NullString + Type string + Amount sql.NullString + Denom sql.NullString + UnsafeBaseDenom sql.NullString + PartitionId int64 +} diff --git a/integration_test_util/types/db/smart_contract.go b/integration_test_util/types/db/smart_contract.go new file mode 100644 index 0000000000..7e700c9bb9 --- /dev/null +++ b/integration_test_util/types/db/smart_contract.go @@ -0,0 +1,47 @@ +package db + +import "database/sql" + +type SmartContractRecord struct { + Height int64 + EvmTxHash string + CreatorEvmAddr string + OriginalInput string + OriginalInputHash string + DeployedBytecode string + DeployedBytecodeHash string + SourceCode sql.NullString + SourceCodeHash sql.NullString + AbiHash sql.NullString + Name sql.NullString + Symbol sql.NullString + LogoUrl sql.NullString + Erc sql.NullString + FetchNameErr sql.NullString + FetchSymbolErr sql.NullString + FetchDecimalsErr sql.NullString + ProxyCurImplAddr sql.NullString + ProxyPrevImplAddr sql.NullString + MinimalProxyImplAddr sql.NullString + VerifiedContractName sql.NullString + VerifiedContractFileName sql.NullString + VerifiedCompilerVersion sql.NullString + VerifiedEvmVersion sql.NullString + VerifiedConstructorArguments sql.NullString + VerifiedLicenseType sql.NullString + VerifyId sql.NullString + PossibleErc20 bool + PossibleErc721 bool + PossibleErc1155 bool + Tracking bool + Decimals int16 + DataVersion int16 + VerifiedEpoch sql.NullInt64 + ProxyCurImplVer sql.NullInt64 + VerifiedOptimizerRuns sql.NullInt64 + ResyncAfterTracking sql.NullBool + VerifiedOptimizerEnabled sql.NullBool + IsInternalCreated sql.NullBool + ImplDetectMethod sql.NullInt16 + VerifiedMatchType sql.NullInt16 +} diff --git a/integration_test_util/types/db/transaction.go b/integration_test_util/types/db/transaction.go new file mode 100644 index 0000000000..69fe1e89d2 --- /dev/null +++ b/integration_test_util/types/db/transaction.go @@ -0,0 +1,30 @@ +package db + +import "database/sql" + +type TransactionRecord struct { + Hash string + Height int64 + OverallSuccessStatus bool + Messages string + Memo sql.NullString + Signatures []string + SignerInfos string + Fee string + GasWanted int64 + GasUsed int64 + RawLog sql.NullString + Logs sql.NullString + PartitionId int64 + Index int16 + Epoch int64 + TotalValueH int64 + TotalValueL int64 + TotalBurntFee int64 + InvolvedAccountAddrs []string + FeePayer string + CountFailedMessages int16 + OriginalSuccessStatus bool + FailedReason sql.NullString + TypeSummary sql.NullString +} diff --git a/integration_test_util/types/keyring.go b/integration_test_util/types/keyring.go new file mode 100644 index 0000000000..44a7beb8c7 --- /dev/null +++ b/integration_test_util/types/keyring.go @@ -0,0 +1,202 @@ +package types + +import ( + "fmt" + cryptohd "github.com/EscanBE/evermint/v12/crypto/hd" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ keyring.Keyring = &itKeyring{} + +type itKeyring struct { + records []*itKeyringRecord +} + +type itKeyringRecord struct { + uid string + record *keyring.Record + testAccount *TestAccount +} + +func NewIntegrationTestKeyring(accounts TestAccounts) keyring.Keyring { + var records []*itKeyringRecord + + for i, account := range accounts { + uid := fmt.Sprintf("it%d", i+1) + + anyPubKey, err := codectypes.NewAnyWithValue(account.GetPubKey()) + if err != nil { + panic(err) + } + records = append(records, &itKeyringRecord{ + uid: uid, + record: &keyring.Record{ + Name: uid, + PubKey: anyPubKey, + Item: nil, + }, + testAccount: account, + }) + } + + return &itKeyring{ + records: records, + } +} + +func (kr *itKeyring) Backend() string { + return "test" +} + +func (kr *itKeyring) List() ([]*keyring.Record, error) { + var records []*keyring.Record + for _, record := range kr.records { + records = append(records, record.record) + } + + return records, nil +} + +func (kr *itKeyring) SupportedAlgorithms() (keyring.SigningAlgoList, keyring.SigningAlgoList) { + return cryptohd.SupportedAlgorithms, cryptohd.SupportedAlgorithmsLedger +} + +func (kr *itKeyring) Key(uid string) (record *keyring.Record, err error) { + for _, keyringRecord := range kr.records { + if keyringRecord.uid == uid { + return keyringRecord.record, nil + } + } + + return nil, fmt.Errorf("keyring record with uid %s not found", uid) +} + +func (kr *itKeyring) KeyByAddress(address sdk.Address) (*keyring.Record, error) { + for _, record := range kr.records { + if record.testAccount.GetCosmosAddress().Equals(address) { + return record.record, nil + } + } + + return nil, fmt.Errorf("keyring record with address %s not found", address.String()) +} + +func (kr *itKeyring) Delete(uid string) error { + var foundAndDeleted bool + var newRecords []*itKeyringRecord + for _, record := range kr.records { + if record.uid == uid { + foundAndDeleted = true + continue + } + + newRecords = append(newRecords, record) + } + if !foundAndDeleted { + return fmt.Errorf("keyring record with uid %s not found", uid) + } + + kr.records = newRecords + return nil +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) DeleteByAddress(address sdk.Address) error { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) Rename(from string, to string) error { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) NewMnemonic(uid string, language keyring.Language, hdPath, bip39Passphrase string, algo keyring.SignatureAlgo) (*keyring.Record, string, error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) NewAccount(uid, mnemonic, bip39Passphrase, hdPath string, algo keyring.SignatureAlgo) (*keyring.Record, error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) SaveLedgerKey(uid string, algo keyring.SignatureAlgo, hrp string, coinType, account, index uint32) (*keyring.Record, error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) SaveOfflineKey(uid string, pubkey cryptotypes.PubKey) (*keyring.Record, error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) SaveMultisig(uid string, pubkey cryptotypes.PubKey) (*keyring.Record, error) { + panic("implement me") +} + +func (kr *itKeyring) Sign(uid string, msg []byte) ([]byte, cryptotypes.PubKey, error) { + for _, record := range kr.records { + if record.uid == uid { + return kr.sign(record, msg) + } + } + + return nil, nil, fmt.Errorf("keyring record with uid %s not found", uid) +} + +func (kr *itKeyring) SignByAddress(address sdk.Address, msg []byte) ([]byte, cryptotypes.PubKey, error) { + for _, record := range kr.records { + if record.testAccount.GetCosmosAddress().Equals(address) { + return kr.sign(record, msg) + } + } + + return nil, nil, fmt.Errorf("keyring record with address %s not found", address.String()) +} + +func (kr *itKeyring) sign(record *itKeyringRecord, msg []byte) ([]byte, cryptotypes.PubKey, error) { + return record.testAccount.Signer.SignByAddress(record.testAccount.GetCosmosAddress(), msg) +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ImportPrivKey(uid, armor, passphrase string) error { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ImportPrivKeyHex(uid, privKey, algoStr string) error { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ImportPubKey(uid string, armor string) error { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ExportPubKeyArmor(uid string) (string, error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ExportPubKeyArmorByAddress(address sdk.Address) (string, error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error) { + panic("implement me") +} + +//goland:noinspection GoUnusedParameter +func (kr *itKeyring) ExportPrivKeyArmorByAddress(address sdk.Address, encryptPassphrase string) (armor string, err error) { + panic("implement me") +} + +func (kr *itKeyring) MigrateAll() ([]*keyring.Record, error) { + panic("implement me") +} diff --git a/integration_test_util/types/mem_db.go b/integration_test_util/types/mem_db.go new file mode 100644 index 0000000000..a547cf328a --- /dev/null +++ b/integration_test_util/types/mem_db.go @@ -0,0 +1,80 @@ +package types + +import ( + cdb "github.com/cometbft/cometbft-db" + // tdb "github.com/tendermint/tm-db" +) + +var _ cdb.DB = (*MemDB)(nil) + +// MemDB is a wrapper of Tendermint/CometBFT DB that is backward-compatible with CometBFT chains pre-rename package. +// +// (eg: replace github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.29) +type MemDB struct { + cDb cdb.DB + // tmDb tdb.DB +} + +//func WrapTendermintDB(tmDb tdb.DB) *MemDB { +// return &MemDB{tmDb: tmDb} +//} + +func WrapCometBftDB(cDb cdb.DB) *MemDB { + return &MemDB{cDb: cDb} +} + +func (w *MemDB) AsCometBFT() cdb.DB { + return w +} + +//func (w *MemDB) AsTendermint() tdb.DB { +// return w.tmDb +//} + +func (w *MemDB) Get(bytes []byte) ([]byte, error) { + return w.cDb.Get(bytes) +} + +func (w *MemDB) Has(key []byte) (bool, error) { + return w.cDb.Has(key) +} + +func (w *MemDB) Set(bytes []byte, bytes2 []byte) error { + return w.cDb.Set(bytes, bytes2) +} + +func (w *MemDB) SetSync(bytes []byte, bytes2 []byte) error { + return w.cDb.SetSync(bytes, bytes2) +} + +func (w *MemDB) Delete(bytes []byte) error { + return w.cDb.Delete(bytes) +} + +func (w *MemDB) DeleteSync(bytes []byte) error { + return w.cDb.DeleteSync(bytes) +} + +func (w *MemDB) Iterator(start, end []byte) (cdb.Iterator, error) { + return w.cDb.Iterator(start, end) +} + +func (w *MemDB) ReverseIterator(start, end []byte) (cdb.Iterator, error) { + return w.cDb.ReverseIterator(start, end) +} + +func (w *MemDB) Close() error { + return w.cDb.Close() +} + +func (w *MemDB) NewBatch() cdb.Batch { + return w.cDb.NewBatch() +} + +func (w *MemDB) Print() error { + return w.cDb.Print() +} + +func (w *MemDB) Stats() map[string]string { + return w.cDb.Stats() +} diff --git a/integration_test_util/types/query_clients.go b/integration_test_util/types/query_clients.go new file mode 100644 index 0000000000..7f319ff07c --- /dev/null +++ b/integration_test_util/types/query_clients.go @@ -0,0 +1,38 @@ +package types + +//goland:noinspection SpellCheckingInspection +import ( + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + httpclient "github.com/cometbft/cometbft/rpc/client/http" + cosmosclient "github.com/cosmos/cosmos-sdk/client" + cosmostxtypes "github.com/cosmos/cosmos-sdk/types/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypeslegacy "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + grpc1 "github.com/cosmos/gogoproto/grpc" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" +) + +type QueryClients struct { + GrpcConnection grpc1.ClientConn + ClientQueryCtx cosmosclient.Context + TendermintRpcHttpClient *httpclient.HTTP + Auth authtypes.QueryClient + Bank banktypes.QueryClient + Distribution disttypes.QueryClient + Erc20 erc20types.QueryClient + EVM evmtypes.QueryClient + GovV1 govtypesv1.QueryClient + GovLegacy govtypeslegacy.QueryClient + IbcTransfer ibctransfertypes.QueryClient + Slashing slashingtypes.QueryClient + Staking stakingtypes.QueryClient + ServiceClient cosmostxtypes.ServiceClient + Rpc *rpctypes.QueryClient +} diff --git a/integration_test_util/types/response_deliver_eth_tx.go b/integration_test_util/types/response_deliver_eth_tx.go new file mode 100644 index 0000000000..2ac105c4ee --- /dev/null +++ b/integration_test_util/types/response_deliver_eth_tx.go @@ -0,0 +1,46 @@ +package types + +import ( + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + abci "github.com/cometbft/cometbft/abci/types" +) + +type ResponseDeliverEthTx struct { + CosmosTxHash string + EthTxHash string + EvmError string + ResponseDeliverEthTx *abci.ResponseDeliverTx +} + +func NewResponseDeliverEthTx(responseDeliverTx *abci.ResponseDeliverTx) *ResponseDeliverEthTx { + if responseDeliverTx == nil { + return nil + } + + response := &ResponseDeliverEthTx{ + ResponseDeliverEthTx: responseDeliverTx, + } + + for _, event := range responseDeliverTx.Events { + if event.Type == evmtypes.TypeMsgEthereumTx { + for _, attribute := range event.Attributes { + //fmt.Println(evmtypes.TypeMsgEthereumTx, "attribute.Key", attribute.Key, "attribute.Value", attribute.Value) + if attribute.Key == evmtypes.AttributeKeyTxHash { + if len(attribute.Value) > 0 && response.CosmosTxHash == "" { + response.CosmosTxHash = attribute.Value + } + } else if attribute.Key == evmtypes.AttributeKeyEthereumTxHash { + if len(attribute.Value) > 0 && response.EthTxHash == "" { + response.EthTxHash = attribute.Value + } + } else if attribute.Key == evmtypes.AttributeKeyEthereumTxFailed { + if len(attribute.Value) > 0 && response.EvmError == "" { + response.EvmError = attribute.Value + } + } + } + } + } + + return response +} diff --git a/integration_test_util/types/signer.go b/integration_test_util/types/signer.go new file mode 100644 index 0000000000..09e7cd3266 --- /dev/null +++ b/integration_test_util/types/signer.go @@ -0,0 +1,50 @@ +package types + +import ( + "fmt" + "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ keyring.Signer = &signer{} + +// signer defines a type that is used on testing for signing MsgEthereumTx +type signer struct { + privKey cryptotypes.PrivKey +} + +func NewSigner(sk cryptotypes.PrivKey) keyring.Signer { + if sk.Type() != ethsecp256k1.KeyType { + panic(fmt.Sprintf( + "require key type %s, got %s", + ethsecp256k1.KeyType, + sk.Type(), + )) + } + + return &signer{ + privKey: sk, + } +} + +// Sign signs the message using the underlying private key +func (s signer) Sign(_ string, msg []byte) ([]byte, cryptotypes.PubKey, error) { + sig, err := s.privKey.Sign(msg) + if err != nil { + return nil, nil, err + } + + return sig, s.privKey.PubKey(), nil +} + +// SignByAddress sign byte messages with a user key providing the address. +func (s signer) SignByAddress(address sdk.Address, msg []byte) ([]byte, cryptotypes.PubKey, error) { + signerAddress := sdk.AccAddress(s.privKey.PubKey().Address()) + if !signerAddress.Equals(address) { + return nil, nil, fmt.Errorf("address mismatch: signer %s ≠ given address %s", signerAddress, address) + } + + return s.Sign("", msg) +} diff --git a/integration_test_util/types/temporary.go b/integration_test_util/types/temporary.go new file mode 100644 index 0000000000..7a8ac7591f --- /dev/null +++ b/integration_test_util/types/temporary.go @@ -0,0 +1,44 @@ +package types + +import ( + "fmt" + tmtypes "github.com/cometbft/cometbft/types" + "path" + "strings" +) + +type TemporaryHolder struct { + files []string + tendermintGenesisDoc *tmtypes.GenesisDoc +} + +func NewTemporaryHolder() *TemporaryHolder { + return &TemporaryHolder{} +} + +func (h *TemporaryHolder) AddTempFile(file string) { + // TODO ES improve this to be able to work on Windows + if len(file) < 1 { + return + } + if !strings.HasPrefix(file, "/tmp/") { + panic(fmt.Sprintf("temp file must be in '/tmp': %s", file)) + } + _, name := path.Split(file) + if !strings.Contains(name, ".tmp") { + panic(fmt.Sprintf("temp file must contains part in '.tmp': %s", file)) + } + h.files = append(h.files, file) +} + +func (h *TemporaryHolder) CacheGenesisDoc(doc *tmtypes.GenesisDoc) { + h.tendermintGenesisDoc = doc +} + +func (h *TemporaryHolder) GetTempFiles() ([]string, bool) { + return h.files, len(h.files) > 0 +} + +func (h *TemporaryHolder) GetCachedGenesisDoc() (*tmtypes.GenesisDoc, bool) { + return h.tendermintGenesisDoc, h.tendermintGenesisDoc != nil +} diff --git a/integration_test_util/types/tendermint_app.go b/integration_test_util/types/tendermint_app.go new file mode 100644 index 0000000000..eb339661ec --- /dev/null +++ b/integration_test_util/types/tendermint_app.go @@ -0,0 +1,9 @@ +package types + +import nm "github.com/cometbft/cometbft/node" + +type TendermintApp interface { + TendermintNode() *nm.Node + GetRpcAddr() (addr string, supported bool) + Shutdown() +} diff --git a/integration_test_util/types/tendermint_app_imp.go b/integration_test_util/types/tendermint_app_imp.go new file mode 100644 index 0000000000..57c0621543 --- /dev/null +++ b/integration_test_util/types/tendermint_app_imp.go @@ -0,0 +1,49 @@ +package types + +import ( + "fmt" + nm "github.com/cometbft/cometbft/node" + "strings" +) + +var _ TendermintApp = &tendermintAppImp{} + +type tendermintAppImp struct { + tendermintNode *nm.Node + rpcAddr string + grpcAddr string +} + +func NewTendermintApp(tendermintNode *nm.Node, rpcPort int) TendermintApp { + app := &tendermintAppImp{ + tendermintNode: tendermintNode, + } + if rpcPort > 0 { + app.rpcAddr = fmt.Sprintf("tcp://localhost:%d", rpcPort) + } + return app +} + +func (a *tendermintAppImp) TendermintNode() *nm.Node { + return a.tendermintNode +} + +func (a *tendermintAppImp) GetRpcAddr() (addr string, supported bool) { + return a.rpcAddr, a.rpcAddr != "" +} + +func (a *tendermintAppImp) Shutdown() { + if a == nil || a.tendermintNode == nil || !a.tendermintNode.IsRunning() { + return + } + err := a.tendermintNode.Stop() + if err != nil { + if strings.Contains(err.Error(), "already stopped") { + // ignore + } else { + fmt.Println("Failed to stop tendermint node") + fmt.Println(err) + } + } + a.tendermintNode.Wait() +} diff --git a/integration_test_util/utils/crypto.go b/integration_test_util/utils/crypto.go new file mode 100644 index 0000000000..67c4fa31da --- /dev/null +++ b/integration_test_util/utils/crypto.go @@ -0,0 +1,12 @@ +package utils + +//goland:noinspection SpellCheckingInspection +import ( + "encoding/hex" + gethcrypto "github.com/ethereum/go-ethereum/crypto" +) + +// Keccak256 is function used in testing only, it has the dedicated implementation for purpose of double-check result +func Keccak256(input string) string { + return hex.EncodeToString(gethcrypto.Keccak256Hash([]byte(input)).Bytes()) +} diff --git a/integration_test_util/utils/number.go b/integration_test_util/utils/number.go new file mode 100644 index 0000000000..5d1d526f14 --- /dev/null +++ b/integration_test_util/utils/number.go @@ -0,0 +1,28 @@ +package utils + +import ( + cryptorand "crypto/rand" + "github.com/pkg/errors" + "math/rand" +) + +// RandomPositiveInt64 returns a random int64, value >= 0. +func RandomPositiveInt64() int64 { + val := rand.Int63() + if val < 0 { + val = -val + } + return val +} + +// GenRandomBytes returns generated random bytes array, with specified length. +func GenRandomBytes(size int) []byte { + bz := make([]byte, size) + if size > 0 { + _, err := cryptorand.Read(bz) + if err != nil { + panic(errors.Wrap(err, "failed to generate random bytes")) + } + } + return bz +} diff --git a/integration_test_util/utils/port.go b/integration_test_util/utils/port.go new file mode 100644 index 0000000000..4ed2b3233f --- /dev/null +++ b/integration_test_util/utils/port.go @@ -0,0 +1,70 @@ +package utils + +import ( + "fmt" + "net" + "strings" + "sync" + "time" +) + +var muPort = &sync.RWMutex{} +var curPort = 10_000 + +const maxPort = 65535 + +// GetNextPortAvailable check if port is available and return it. If not available, it will check the next port. +func GetNextPortAvailable() int { + muPort.Lock() + defer muPort.Unlock() + + retry := 1000 + + for { + curPort++ + retry-- + if retry < 1 { + panic("too many retries allocating port") + } + if curPort > maxPort { + curPort = 10_000 + } + closed, err := isPortClosed(curPort) + if err != nil { + continue + } + if closed { + return curPort + } + } +} + +// isPortClosed checks if given port is closed. +func isPortClosed(port int) (closed bool, err error) { + closed = true // default: treating as closed + + var conn net.Conn + + conn, err = net.DialTimeout("tcp", net.JoinHostPort("localhost", fmt.Sprintf("%d", port)), time.Second) + if err != nil { + errMsg := err.Error() + dialingError := strings.Contains(errMsg, "error while dialing") || strings.Contains(errMsg, "connection refused") + if dialingError { + closed = true + err = nil + } else { + closed = false + } + } else { + if conn != nil { + defer func() { + _ = conn.Close() + }() + closed = false + } else { + closed = false + } + } + + return +} diff --git a/integration_test_util/utils/tendermint.go b/integration_test_util/utils/tendermint.go new file mode 100644 index 0000000000..4c3fd4bfe2 --- /dev/null +++ b/integration_test_util/utils/tendermint.go @@ -0,0 +1,137 @@ +package utils + +//goland:noinspection SpellCheckingInspection +import ( + "context" + "fmt" + "github.com/EscanBE/evermint/v12/constants" + cdb "github.com/cometbft/cometbft-db" + abci "github.com/cometbft/cometbft/abci/types" + tmcrypto "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/libs/log" + nm "github.com/cometbft/cometbft/node" + "github.com/cometbft/cometbft/p2p" + "github.com/cometbft/cometbft/privval" + "github.com/cometbft/cometbft/proxy" + tmcorestypes "github.com/cometbft/cometbft/rpc/core/types" + tmgrpc "github.com/cometbft/cometbft/rpc/grpc" + tmrpcclient "github.com/cometbft/cometbft/rpc/jsonrpc/client" + rpctest "github.com/cometbft/cometbft/rpc/test" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/google/uuid" + "time" +) + +// StartTendermintNode starts a Tendermint node for the given ABCI Application, used for testing purposes. +func StartTendermintNode(app abci.Application, genesis *tmtypes.GenesisDoc, db cdb.DB, validatorPrivKey tmcrypto.PrivKey, logger log.Logger) (tendermintNode *nm.Node, rpcPort int, tempFiles []string) { + if app == nil { + panic("missing app") + } + if genesis == nil { + panic("missing genesis") + } + if db == nil { + panic("missing db") + } + if validatorPrivKey == nil { + panic("missing validator private key") + } + + useRpc := true + useGrpc := false + + // Create & start node + config := rpctest.GetConfig(false) + + // timeout commit is not a big, not a small number, but enough to broadcast amount of txs + config.Consensus.TimeoutCommit = 500 * time.Millisecond + config.Consensus.SkipTimeoutCommit = false // don't use default (which is true), because the block procedures too fast + + var portRpc, portGrpc int + + config.ProxyApp = fmt.Sprintf("tcp://localhost:%d", GetNextPortAvailable()) + + if useRpc { + portRpc = GetNextPortAvailable() + config.RPC.ListenAddress = fmt.Sprintf("tcp://localhost:%d", portRpc) + } else { + config.RPC.ListenAddress = "" + } + + if useGrpc { + portGrpc = GetNextPortAvailable() + config.RPC.GRPCListenAddress = fmt.Sprintf("tcp://localhost:%d", portGrpc) + } else { + config.RPC.GRPCListenAddress = "" + } + + config.RPC.PprofListenAddress = "" // fmt.Sprintf("tcp://localhost:%d", GetNextPortAvailable()) + + config.P2P.ListenAddress = fmt.Sprintf("tcp://localhost:%d", GetNextPortAvailable()) + + randomStateFilePath := fmt.Sprintf("/tmp/%s-tendermint-state-file-%s.tmp.json", constants.ApplicationBinaryName, uuid.New().String()) + tempFiles = append(tempFiles, randomStateFilePath) + pv := privval.NewFilePV(validatorPrivKey, "", randomStateFilePath) + pApp := proxy.NewLocalClientCreator(app) + nodeKey := &p2p.NodeKey{ + PrivKey: pv.Key.PrivKey, + } + + var genesisProvider nm.GenesisDocProvider = func() (*tmtypes.GenesisDoc, error) { + return genesis, nil + } + + node, err := nm.NewNode( + config, // config + pv, // private validator + nodeKey, // node key + pApp, // client creator + genesisProvider, // genesis doc provider + func(_ *nm.DBContext) (cdb.DB, error) { // db provider + return db, nil + }, + nm.DefaultMetricsProvider(config.Instrumentation), // metrics provider + logger, // logger + ) + if err != nil { + panic(err) + } + err = node.Start() + if err != nil { + panic(err) + } + + waitForRPC := func() { + client, err := tmrpcclient.New(config.RPC.ListenAddress) + if err != nil { + panic(err) + } + result := new(tmcorestypes.ResultStatus) + for { + _, err := client.Call(context.Background(), "status", map[string]interface{}{}, result) + if err == nil { + return + } + + fmt.Println("error", err) + time.Sleep(time.Millisecond) + } + } + waitForGRPC := func() { + client := tmgrpc.StartGRPCClient(config.RPC.GRPCListenAddress) + for { + _, err := client.Ping(context.Background(), &tmgrpc.RequestPing{}) + if err == nil { + return + } + } + } + if useRpc { + waitForRPC() + } + if useGrpc { + waitForGRPC() + } + + return node, portRpc, tempFiles +} From 61d36db3476f1762bd90b2e6300d98aa9f253aef Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Wed, 10 Jan 2024 16:46:24 +0700 Subject: [PATCH 2/3] add IT suite for eth_ RPC --- .../eth_rpc_it_suite/eth_api_account_test.go | 302 ++++++ .../eth_rpc_it_suite/eth_api_block_test.go | 773 ++++++++++++++++ .../eth_api_transaction_test.go | 862 ++++++++++++++++++ .../setup_eth_rpc_integration_test.go | 79 ++ 4 files changed, 2016 insertions(+) create mode 100644 rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_account_test.go create mode 100644 rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_block_test.go create mode 100644 rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_transaction_test.go create mode 100644 rpc/namespaces/ethereum/eth/eth_rpc_it_suite/setup_eth_rpc_integration_test.go diff --git a/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_account_test.go b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_account_test.go new file mode 100644 index 0000000000..f481eff8e3 --- /dev/null +++ b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_account_test.go @@ -0,0 +1,302 @@ +package demo + +import ( + "context" + "fmt" + "github.com/EscanBE/evermint/v12/integration_test_util" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + etherminttypes "github.com/EscanBE/evermint/v12/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "math/big" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *EthRpcTestSuite) Test_Accounts() { + suite.CITS.UseKeyring() + suite.Commit() // ensure keyring is used + + accounts, err := suite.GetEthPublicAPI().Accounts() + suite.Require().NoError(err) + suite.Require().Len(accounts, len(suite.CITS.WalletAccounts)) + + expectedAccounts := make(map[string]bool) + for _, account := range suite.CITS.WalletAccounts { + expectedAccounts[account.GetEthAddress().String()] = false + } + + for _, account := range accounts { + _, ok := expectedAccounts[account.String()] + suite.Require().True(ok, "unexpected account %s", account.String()) + expectedAccounts[account.String()] = true + } + + for account, ok := range expectedAccounts { + suite.True(ok, "expected account %s not found", account) + } +} + +func (suite *EthRpcTestSuite) Test_GetBalance() { + type historicalTestcase struct { + rpctypes.BlockNumberOrHash + originalBlockHeight int64 + expectedBalance *big.Int + } + + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(100)) // prepare for multi txs + + var historicalTestcases []historicalTestcase + + for i := 0; i < 5; i++ { + suite.CITS.WaitNextBlockOrCommit() + + beforeReceive := suite.CITS.QueryBalanceFromStore(0, receiver.GetCosmosAddress()) + + err := suite.CITS.TxSend(sender, receiver, 1) + suite.Require().NoError(err) + + suite.CITS.Commit() // ensure tx is included in block + + afterReceived := suite.CITS.QueryBalanceFromStore(0, receiver.GetCosmosAddress()) + + suite.Require().False((*afterReceived).IsEqual(*beforeReceive), "receiver balance must be changed") + + currentHeight := suite.CITS.GetLatestBlockHeight() + currentBlockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), nil) + suite.Require().NoError(err) + suite.Require().NotNil(currentBlockResult) + + blockNum := rpctypes.BlockNumber(currentHeight) + blockHash := common.BytesToHash(currentBlockResult.Block.Hash().Bytes()) + historicalTestcases = append(historicalTestcases, + historicalTestcase{ + originalBlockHeight: currentHeight, + BlockNumberOrHash: rpctypes.BlockNumberOrHash{ + BlockNumber: &blockNum, + BlockHash: nil, + }, + expectedBalance: afterReceived.Amount.BigInt(), + }, historicalTestcase{ + originalBlockHeight: currentHeight, + BlockNumberOrHash: rpctypes.BlockNumberOrHash{ + BlockNumber: nil, + BlockHash: &blockHash, + }, + expectedBalance: afterReceived.Amount.BigInt(), + }, + ) + } + + suite.Commit() + + finalBalance := suite.CITS.QueryBalanceFromStore(0, receiver.GetCosmosAddress()) + + for _, tt := range historicalTestcases { + if tt.BlockNumberOrHash.BlockNumber != nil { + suite.Run(fmt.Sprintf("block height %d, GetBalance using BlockNumber", tt.originalBlockHeight), func() { + gotBalance, err := suite.GetEthPublicAPIAt(tt.originalBlockHeight).GetBalance(receiver.GetEthAddress(), tt.BlockNumberOrHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotBalance) + + suite.Zerof(tt.expectedBalance.Cmp(gotBalance.ToInt()), "expected balance %s at block %d, got %s", tt.expectedBalance.String(), tt.originalBlockHeight, gotBalance.ToInt().String()) + }) + } + if tt.BlockNumberOrHash.BlockHash != nil { + suite.Run(fmt.Sprintf("block height %d, GetBalance using BlockHash", tt.originalBlockHeight), func() { + gotBalance, err := suite.GetEthPublicAPIAt(tt.originalBlockHeight).GetBalance(receiver.GetEthAddress(), tt.BlockNumberOrHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotBalance) + + suite.Zerof(tt.expectedBalance.Cmp(gotBalance.ToInt()), "expected balance %s at block %d, got %s", tt.expectedBalance.String(), tt.originalBlockHeight, gotBalance.ToInt().String()) + }) + } + } + + latestBlockNumber := rpctypes.EthLatestBlockNumber + gotBalance, err := suite.GetEthPublicAPI().GetBalance(receiver.GetEthAddress(), rpctypes.BlockNumberOrHash{ + BlockNumber: &latestBlockNumber, + }) + suite.Require().NoError(err) + suite.Require().NotNil(gotBalance) + suite.Zerof(finalBalance.Amount.BigInt().Cmp(gotBalance.ToInt()), "expected balance %s at latest, got %s", finalBalance.Amount.String(), gotBalance.ToInt().String()) +} + +func (suite *EthRpcTestSuite) Test_GetStorage() { + type historicalTestcase struct { + rpctypes.BlockNumberOrHash + originalBlockHeight int64 + key string + expectedValue string + } + + deployer := suite.CITS.WalletAccounts.Number(1) + + suite.CITS.MintCoin(deployer, suite.CITS.NewBaseCoin(100)) // prepare for multi txs + + contractAddr, _, _, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Require().NoError(err) + + storages := suite.App().EvmKeeper().GetAccountStorage(suite.Ctx(), contractAddr) + suite.Require().Empty(storages, "new contract shouldn't have storage") + + var historicalTestcases []historicalTestcase + + for number := 5; number <= 10; number++ { + suite.CITS.WaitNextBlockOrCommit() + + storagesBefore := suite.App().EvmKeeper().GetAccountStorage(suite.Ctx(), contractAddr) + + callData, err := integration_test_util.Contract1Storage.ABI.Pack("store", big.NewInt(int64(number))) + suite.Require().NoError(err) + _, _, err = suite.CITS.TxSendEvmTx(suite.Ctx(), deployer, &contractAddr, nil, callData) + suite.Require().NoError(err) + + suite.CITS.Commit() // ensure tx is included in block + + storagesLater := suite.App().EvmKeeper().GetAccountStorage(suite.Ctx(), contractAddr) + suite.Require().NotEmpty(storagesLater, "contract should have storage at this point") + + suite.Require().NotEqual(storagesBefore, storagesLater, "storage of contract must be changed") + + currentHeight := suite.CITS.GetLatestBlockHeight() + currentBlockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), nil) + suite.Require().NoError(err) + suite.Require().NotNil(currentBlockResult) + + blockNum := rpctypes.BlockNumber(currentHeight) + blockHash := common.BytesToHash(currentBlockResult.Block.Hash().Bytes()) + + for _, storage := range storagesLater { + historicalTestcases = append(historicalTestcases, + historicalTestcase{ + originalBlockHeight: currentHeight, + BlockNumberOrHash: rpctypes.BlockNumberOrHash{ + BlockNumber: &blockNum, + }, + key: storage.Key, + expectedValue: storage.Value, + }, + historicalTestcase{ + originalBlockHeight: currentHeight, + BlockNumberOrHash: rpctypes.BlockNumberOrHash{ + BlockHash: &blockHash, + }, + key: storage.Key, + expectedValue: storage.Value, + }, + ) + } + } + + suite.Commit() + + finalStorage := suite.App().EvmKeeper().GetAccountStorage(suite.Ctx(), contractAddr) + + for _, tt := range historicalTestcases { + if tt.BlockNumberOrHash.BlockNumber != nil { + suite.Run(fmt.Sprintf("block height %d, GetAccountStorage using BlockNumber", tt.originalBlockHeight), func() { + gotStorageValue, err := suite.GetEthPublicAPIAt(tt.originalBlockHeight).GetStorageAt(contractAddr, tt.key, tt.BlockNumberOrHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotStorageValue) + + suite.Equal(tt.expectedValue, gotStorageValue.String(), "expected value %s at block %d, got %s, key %s", tt.expectedValue, tt.originalBlockHeight, gotStorageValue.String(), tt.key) + }) + } + if tt.BlockNumberOrHash.BlockHash != nil { + suite.Run(fmt.Sprintf("block height %d, GetAccountStorage using BlockHash", tt.originalBlockHeight), func() { + gotStorageValue, err := suite.GetEthPublicAPIAt(tt.originalBlockHeight).GetStorageAt(contractAddr, tt.key, tt.BlockNumberOrHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotStorageValue) + + suite.Equal(tt.expectedValue, gotStorageValue.String(), "expected value %s at block %d, got %s, key %s", tt.expectedValue, tt.originalBlockHeight, gotStorageValue.String(), tt.key) + }) + } + } + + latestBlockNumber := rpctypes.EthLatestBlockNumber + for _, state := range finalStorage { + gotStorageValue, err := suite.GetEthPublicAPI().GetStorageAt(contractAddr, state.GetKey(), rpctypes.BlockNumberOrHash{ + BlockNumber: &latestBlockNumber, + }) + suite.Require().NoError(err) + suite.Equal(state.GetValue(), gotStorageValue.String()) + } +} + +func (suite *EthRpcTestSuite) Test_GetCode() { + deployer := suite.CITS.WalletAccounts.Number(1) + + suite.CITS.MintCoin(deployer, suite.CITS.NewBaseCoin(100)) // prepare for multi txs + + suite.Commit() + + heightWithoutCode := suite.CITS.GetLatestBlockHeight() + + suite.Commit() + suite.Commit() + + contractAddr, _, _, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Require().NoError(err) + + heightWithCode := suite.CITS.GetLatestBlockHeight() + + suite.Commit() + + accountI := suite.App().AccountKeeper().GetAccount(suite.Ctx(), contractAddr.Bytes()) + suite.Require().NotNil(accountI) + contractAccount, ok := accountI.(*etherminttypes.EthAccount) + suite.Require().True(ok) + + codeHash := common.HexToHash(contractAccount.CodeHash) + + code := suite.App().EvmKeeper().GetCode(suite.Ctx(), codeHash) + suite.Require().NotEmptyf(code, "not found code for contract %s", contractAddr.String()) + + blockWithoutCode, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(heightWithoutCode)) + suite.Require().NoError(err) + suite.Require().NotNil(blockWithoutCode) + + blockWithCode, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(heightWithCode)) + suite.Require().NoError(err) + suite.Require().NotNil(blockWithCode) + + suite.Run("context contract not deployed, fetch by block number", func() { + blockNumberWithoutCode := rpctypes.BlockNumber(heightWithoutCode) + bz, err := suite.GetEthPublicAPIAt(heightWithoutCode).GetCode(contractAddr, rpctypes.BlockNumberOrHash{ + BlockNumber: &blockNumberWithoutCode, + }) + suite.Require().NoError(err) + suite.Empty(bz, "code must be empty at this context") + }) + + suite.Run("context contract not deployed, fetch by block hash", func() { + blockHashWithoutCode := common.BytesToHash(blockWithoutCode.Block.Hash().Bytes()) + bz, err := suite.GetEthPublicAPIAt(heightWithoutCode).GetCode(contractAddr, rpctypes.BlockNumberOrHash{ + BlockHash: &blockHashWithoutCode, + }) + suite.Require().NoError(err) + suite.Empty(bz, "code must be empty at this context") + }) + + suite.Run("context contract deployed, fetch by block number", func() { + blockNumberWithCode := rpctypes.BlockNumber(heightWithCode) + bz, err := suite.GetEthPublicAPIAt(heightWithCode).GetCode(contractAddr, rpctypes.BlockNumberOrHash{ + BlockNumber: &blockNumberWithCode, + }) + suite.Require().NoError(err) + suite.Equal(hexutil.Bytes(code), bz) + }) + + suite.Run("context contract deployed, fetch by block hash", func() { + blockHashWithCode := common.BytesToHash(blockWithCode.Block.Hash().Bytes()) + bz, err := suite.GetEthPublicAPIAt(heightWithCode).GetCode(contractAddr, rpctypes.BlockNumberOrHash{ + BlockHash: &blockHashWithCode, + }) + suite.Require().NoError(err) + suite.Equal(hexutil.Bytes(code), bz) + }) +} diff --git a/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_block_test.go b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_block_test.go new file mode 100644 index 0000000000..2a5158f843 --- /dev/null +++ b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_block_test.go @@ -0,0 +1,773 @@ +package demo + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + tmrpcclient "github.com/cometbft/cometbft/rpc/client" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" + "math/big" + "math/rand" + "strconv" + "strings" + "time" +) + +//goland:noinspection SpellCheckingInspection + +type resultGetBlockStruct struct { + BaseFeePerGas string `json:"baseFeePerGas"` + Difficulty string `json:"difficulty"` + ExtraData string `json:"extraData"` + GasLimit string `json:"gasLimit"` + GasUsed string `json:"gasUsed"` + Hash string `json:"hash"` + LogsBloom string `json:"logsBloom"` + Miner string `json:"miner"` + MixHash string `json:"mixHash"` + Nonce string `json:"nonce"` + Number string `json:"number"` + ParentHash string `json:"parentHash"` + ReceiptsRoot string `json:"receiptsRoot"` + Sha3Uncles string `json:"sha3Uncles"` + Size string `json:"size"` + StateRoot string `json:"stateRoot"` + Timestamp string `json:"timestamp"` + TotalDifficulty string `json:"totalDifficulty"` + Transactions []interface{} `json:"transactions"` + TransactionsRoot string `json:"transactionsRoot"` + Uncles []string `json:"uncles"` +} + +func (suite *EthRpcTestSuite) Test_BlockNumber() { + randomShiftingBlocksCount := int(rand.Uint32()%5 + 2) + + for i := 0; i < randomShiftingBlocksCount; i++ { + suite.Commit() + } + + latestBlockHeight := suite.CITS.GetLatestBlockHeight() + suite.Require().Equal(latestBlockHeight, suite.Ctx().BlockHeight()) + + blockNumber, err := suite.GetEthPublicAPI().BlockNumber() + suite.Require().NoError(err) + + suite.Equal(uint64(latestBlockHeight), uint64(blockNumber)) +} + +func (suite *EthRpcTestSuite) Test_GetBlockByNumberAndHash() { + suite.Run("basic test", func() { + suite.Commit() // require at least 2 blocks + + previousBlockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(1)) + suite.Require().NoError(err) + suite.Require().NotNil(previousBlockResult) + + gotBlockByNumber, err := suite.GetEthPublicAPI().GetBlockByNumber(2, false) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByNumber) + + currentBlockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(2)) + suite.Require().NoError(err) + suite.Require().NotNil(currentBlockResult) + suite.Require().Equal(int64(2), currentBlockResult.Block.Height) + + gotBlockByHash, err := suite.GetEthPublicAPI().GetBlockByHash(common.BytesToHash(currentBlockResult.Block.Hash()), false) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByHash) + + suite.Equal(gotBlockByNumber, gotBlockByHash, "result of eth_getBlockByNumber and eth_getBlockByHash must be same") + + suite.Equal(hexutil.Uint64(currentBlockResult.Block.Height), gotBlockByNumber["number"]) + suite.Equal(hexutil.Bytes(currentBlockResult.Block.Hash()), gotBlockByNumber["hash"], "hash must be Tendermint block hash") + suite.Equal(common.BytesToHash(previousBlockResult.Block.Hash()), gotBlockByNumber["parentHash"], "parentHash must be previous Tendermint block hash") + suite.Equal(hexutil.Bytes(currentBlockResult.Block.AppHash), gotBlockByNumber["stateRoot"], "stateRoot must be Tendermint AppHash") + suite.Equal([]common.Hash{}, gotBlockByNumber["uncles"], "uncles must be empty since it is not possible in PoS chain") + }) + + // this is response-based testing so the test is json based, that's why it is not usually unmarshal to object. + // The ultimate target is ensured response data to end user. + deepTestGetBlockByNumberAndHash := func(fullTxs bool) { + var err error + + // shift some blocks + randomShiftingBlocksCount := int(rand.Uint32()%3 + 3) + for i := 0; i < randomShiftingBlocksCount; i++ { + suite.Commit() + } + + // prepare txs + const evmTxsCount = 2 + const nonEvmTxsCount = 1 + var senderEvmTxs, senderNonEvmTxs []*itutiltypes.TestAccount + // prepare senders and fund them + for num := 1; num <= evmTxsCount; num++ { + sender := integration_test_util.NewTestAccount(suite.T(), nil) + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(10)) + senderEvmTxs = append(senderEvmTxs, sender) + } + for num := 1; num <= nonEvmTxsCount; num++ { + sender := integration_test_util.NewTestAccount(suite.T(), nil) + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(10)) + senderNonEvmTxs = append(senderNonEvmTxs, sender) + } + + // wait new block then send some txs to ensure all txs are included in the same block + suite.CITS.WaitNextBlockOrCommit() + + testBlockHeight := suite.CITS.GetLatestBlockHeight() + + receiver := suite.CITS.WalletAccounts.Number(1) + + msgEvmTxs := make(map[string]*evmtypes.MsgEthereumTx) + + startTime := time.Now().UTC().UnixMilli() + for num := 1; num <= evmTxsCount; num++ { + // Txs must be sent async to ensure same block height + + msgEthereumTx, err := suite.CITS.TxSendViaEVMAsync(senderEvmTxs[num-1], receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + + msgEvmTxs[msgEthereumTx.Hash] = msgEthereumTx + } + + for num := 1; num <= nonEvmTxsCount; num++ { + // Txs must be sent async to ensure same block height + err = suite.CITS.TxSendAsync(senderNonEvmTxs[num-1], receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + } + fmt.Println("Broadcast takes", time.Now().UTC().UnixMilli()-startTime, "ms") + + suite.CITS.WaitNextBlockOrCommit() // finalize the test block + + if testBlockHeight+1 != suite.CITS.GetLatestBlockHeight() { + suite.T().Skip("test skipped because the expected context block number does not matches") + } + + testBlockHeight++ // since txs go to mempool and only included in the next block + + fmt.Println("testBlockHeight", testBlockHeight) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + previousBlockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(testBlockHeight-1)) + suite.Require().NoError(err) + suite.Require().NotNil(previousBlockResult) + + gotBlockByNumber, err := suite.GetEthPublicAPI().GetBlockByNumber(rpctypes.BlockNumber(testBlockHeight), fullTxs) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByNumber) + + blockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(testBlockHeight)) + suite.Require().NoError(err) + suite.Require().NotNil(blockResult) + suite.Require().Equal(evmTxsCount+nonEvmTxsCount, len(blockResult.Block.Txs), "must be same as sent txs count for both EVM & non-EVM txs") + + gotBlockByHash, err := suite.GetEthPublicAPI().GetBlockByHash(common.BytesToHash(blockResult.Block.Hash()), fullTxs) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByHash) + + suite.Equal(gotBlockByNumber, gotBlockByHash, "result of eth_getBlockByNumber and eth_getBlockByHash must be same") + + resultBlockResult, err := suite.CITS.RpcBackend.TendermintBlockResultByNumber(ptrInt64(testBlockHeight)) + suite.Require().NoError(err) + blockBloom, err := suite.CITS.RpcBackend.BlockBloom(resultBlockResult) + suite.Require().NoError(err, "failed to fetch block bloom") + + baseFee := suite.App().FeeMarketKeeper().GetParams(suite.Ctx()).BaseFee + consensusParams, err := suite.CITS.QueryClients.ClientQueryCtx.Client.(tmrpcclient.NetworkClient).ConsensusParams(context.Background(), ptrInt64(testBlockHeight)) + suite.Require().NoError(err, "failed to fetch consensus params of test block") + suite.Equal(int64(40_000_000), consensusParams.ConsensusParams.Block.MaxGas, "invalid setup?") + + bzGotBlockByNumber, err := json.Marshal(gotBlockByNumber) + suite.Require().NoError(err) + + var textResultStruct resultGetBlockStruct + err = json.Unmarshal(bzGotBlockByNumber, &textResultStruct) + suite.Require().NoError(err) + + if len(textResultStruct.Transactions) > 0 { + if fullTxs { + for i, mTx := range textResultStruct.Transactions { + txData, ok := mTx.(map[string]interface{}) + suite.True(ok, "when full-txs mode, tx list must be the tx data itself") + + txHash, ok := txData["hash"].(string) + suite.True(ok, "invalid tx content") + + msgEvmTx, ok := msgEvmTxs[txHash] + suite.Truef(ok, "tx %s could not be found", txHash) + + bz, err := json.Marshal(txData) + suite.Require().NoError(err) + + var ethRpcTxs rpctypes.RPCTransaction + err = json.Unmarshal(bz, ðRpcTxs) + suite.Require().NoError(err, "failed to unmarshal to RPCTransaction") + + tx := msgEvmTx.AsTransaction() + + if suite.NotNil(txData["blockHash"]) { + if suite.IsType("string", txData["blockHash"]) { + suite.Equal("0x"+strings.ToLower(blockResult.Block.Hash().String()), txData["blockHash"]) + } + } + if suite.NotNil(txData["blockNumber"]) { + if suite.IsType("string", txData["blockNumber"]) { + suite.Equal(fmt.Sprintf("0x%x", blockResult.Block.Height), txData["blockNumber"]) + } + } + if suite.NotNil(txData["from"]) { + if suite.IsType("string", txData["from"]) { + suite.Len(txData["from"].(string), 42) + suite.NotEqual("0x0000000000000000000000000000000000000000", txData["from"]) + } + } + if suite.NotNil(txData["gas"]) { + if suite.IsType("string", txData["gas"]) { + suite.Equal(fmt.Sprintf("0x%x", tx.Gas()), txData["gas"]) + } + } + if suite.NotNil(txData["gasPrice"]) { + if suite.IsType("string", txData["gasPrice"]) { + suite.Contains(txData["gasPrice"].(string), "0x") + } + } + if suite.NotNil(txData["hash"]) { + if suite.IsType("string", txData["hash"]) { + suite.Equal(strings.ToLower(tx.Hash().String()), txData["hash"]) + } + } + if suite.NotNil(txData["to"]) { + if suite.IsType("string", txData["to"]) { + suite.Equal(strings.ToLower(receiver.GetEthAddress().String()), txData["to"]) + } + } + if suite.NotNil(txData["nonce"]) { + if suite.IsType("string", txData["nonce"]) { + suite.Equal("0x0", txData["nonce"]) + } + } + if suite.NotNil(txData["input"]) { + if suite.IsType("string", txData["input"]) { + suite.Contains(txData["input"].(string), "0x") + suite.Equal("0x", txData["input"]) + } + } + if suite.NotNil(txData["transactionIndex"]) { + if suite.IsType("string", txData["transactionIndex"]) { + suite.Equal(fmt.Sprintf("0x%x", i), txData["transactionIndex"]) + } + } + if suite.NotNil(txData["value"]) { + if suite.IsType("string", txData["value"]) { + suite.Equal(fmt.Sprintf("0x%x", suite.CITS.NewBaseCoin(1).Amount.Int64()), txData["value"]) + } + } + if suite.NotNil(txData["type"]) { + if suite.IsType("string", txData["type"]) { + suite.Equal(fmt.Sprintf("0x%x", tx.Type()), txData["type"]) + } + } + if accessList, found := txData["accessList"]; found { + suite.Empty(accessList) + } + if suite.NotNil(txData["chainId"]) { + if suite.IsType("string", txData["chainId"]) { + suite.Equal(fmt.Sprintf("0x%x", suite.App().EvmKeeper().ChainID().Int64()), txData["chainId"]) + } + } + v, r, s := tx.RawSignatureValues() + if suite.NotNil(txData["v"]) { + if suite.IsType("string", txData["v"]) { + if v.Sign() == 0 { + suite.Equal("0x0", txData["v"]) + } else { + suite.Equal(fmt.Sprintf("0x%x", v), txData["v"]) + } + } + } + if suite.NotNil(txData["r"]) { + if suite.IsType("string", txData["r"]) { + if r.Sign() == 0 { + suite.Equal("0x0", txData["r"]) + } else { + suite.Equal(fmt.Sprintf("0x%x", r), txData["r"]) + } + } + } + if suite.NotNil(txData["s"]) { + if suite.IsType("string", txData["s"]) { + if s.Sign() == 0 { + suite.Equal("0x0", txData["s"]) + } else { + suite.Equal(fmt.Sprintf("0x%x", s), txData["s"]) + } + } + } + } + } else { + for _, tx := range textResultStruct.Transactions { + txHash, ok := tx.(string) + suite.True(ok, "when Not full-txs mode, tx list must be tx hash") + _, ok = msgEvmTxs[txHash] + suite.Truef(ok, "tx %s could not be found", txHash) + } + } + } + + suite.Equal(fmt.Sprintf("0x%x", baseFee.Int64()), textResultStruct.BaseFeePerGas) + suite.Equal("0x0", textResultStruct.Difficulty, "difficulty must be zero since PoS chain does not have this") + suite.Equal("0x", textResultStruct.ExtraData) + suite.Equal(fmt.Sprintf("0x%x", consensusParams.ConsensusParams.Block.MaxGas), textResultStruct.GasLimit) + suite.NotEqual("0x0", textResultStruct.GasUsed, "gasUsed must not be zero since there are some txs") + suite.Equal("0x"+hex.EncodeToString(blockResult.Block.Hash()), textResultStruct.Hash, "hash must be Tendermint block hash") + suite.Equal(fmt.Sprintf("0x%x", blockBloom.Bytes()), textResultStruct.LogsBloom) + suite.Equal(strings.ToLower(suite.CITS.ValidatorAccounts.Number(1).GetEthAddress().String()), textResultStruct.Miner, "mis-match validator address as miner or must be lower-case") // Tendermint node uses the first pre-defined validator + suite.Equal("0x0000000000000000000000000000000000000000000000000000000000000000", textResultStruct.MixHash, "mixHash must be zero since PoS chain does not have this") + suite.Equal("0x0000000000000000", textResultStruct.Nonce, "nonce must be zero since PoS chain does not have this") + suite.Equal(fmt.Sprintf("0x%x", testBlockHeight), textResultStruct.Number) + suite.Equal("0x"+hex.EncodeToString(previousBlockResult.Block.Hash()), textResultStruct.ParentHash, "parentHash must be previous Tendermint block hash") + suite.Equal(func() string { // TODO ES fix the RPC to return correct receipt root + var receipts ethtypes.Receipts + for _, tx := range textResultStruct.Transactions { + var transaction *ethtypes.Transaction + if fullTxs { + mTx, ok := tx.(map[string]interface{}) + suite.Require().True(ok) + txHash, ok := mTx["hash"].(string) + suite.Require().True(ok) + transaction = msgEvmTxs[txHash].AsTransaction() + } else { + txHash, ok := tx.(string) + suite.Require().True(ok) + transaction = msgEvmTxs[txHash].AsTransaction() + } + receipts = append(receipts, suite.GetTxReceipt(transaction.Hash())) + } + + return ethtypes.DeriveSha(receipts, trie.NewStackTrie(nil)).String() + }(), textResultStruct.ReceiptsRoot, "mis-match receipt root") + suite.Equal("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", textResultStruct.Sha3Uncles, "sha3Uncles must be value of EmptyUncleHash") + suite.Equal(fmt.Sprintf("0x%x", blockResult.Block.Size()), textResultStruct.Size, "must must be Tendermint block size") + suite.Equal("0x"+hex.EncodeToString(blockResult.Block.AppHash.Bytes()), textResultStruct.StateRoot, "stateRoot must be Tendermint AppHash") + suite.Equal(fmt.Sprintf("0x%x", blockResult.Block.Time.UTC().Unix()), textResultStruct.Timestamp, "timestamp must be block UTC epoch seconds") + suite.Equal("0x0", textResultStruct.TotalDifficulty, "total difficulty must be zero since PoS chain does not have this") + suite.Len(textResultStruct.Transactions, evmTxsCount, "transaction list must be same as sent EVM txs") + suite.Equal(func() string { // TODO ES fix the RPC to return correct transaction root + var transactions ethtypes.Transactions + for _, tx := range textResultStruct.Transactions { + var transaction *ethtypes.Transaction + if fullTxs { + mTx, ok := tx.(map[string]interface{}) + suite.Require().True(ok) + txHash, ok := mTx["hash"].(string) + suite.Require().True(ok) + transaction = msgEvmTxs[txHash].AsTransaction() + } else { + txHash, ok := tx.(string) + suite.Require().True(ok) + transaction = msgEvmTxs[txHash].AsTransaction() + } + transactions = append(transactions, transaction) + } + + return ethtypes.DeriveSha(transactions, trie.NewStackTrie(nil)).String() + }(), textResultStruct.TransactionsRoot, "mis-match transaction root") + suite.Empty(textResultStruct.Uncles, "uncles must be empty since it is not possible in PoS chain") + } + + suite.Run("deep test, not full txs", func() { + deepTestGetBlockByNumberAndHash(false) + }) + + suite.Run("deep test, full txs", func() { + deepTestGetBlockByNumberAndHash(true) + }) + + suite.Run("test access list & nonce of txs in full-tx mode", func() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(100)) + + evmKeeper := suite.App().EvmKeeper() + nonceSender := evmKeeper.GetNonce(suite.Ctx(), sender.GetEthAddress()) + + suite.Commit() + + err := suite.CITS.TxSend(sender, receiver, 1) + suite.Require().NoError(err) + nonceSender++ + + suite.Commit() + + evmTxArgs := &evmtypes.EvmTxArgs{ + ChainID: evmKeeper.ChainID(), + Nonce: nonceSender, + GasLimit: 300_000, + GasFeeCap: suite.App().FeeMarketKeeper().GetBaseFee(suite.Ctx()), + GasTipCap: big.NewInt(1), + To: func() *common.Address { + ethAddr := receiver.GetEthAddress() + return ðAddr + }(), + Amount: suite.CITS.NewBaseCoin(1).Amount.BigInt(), + Input: nil, + Accesses: ðtypes.AccessList{ + { + Address: sender.GetEthAddress(), + }, + { + Address: receiver.GetEthAddress(), + }, + }, + } + suite.Require().Len(*evmTxArgs.Accesses, 2) + + msgEthereumTx := evmtypes.NewTx(evmTxArgs) + msgEthereumTx.From = sender.GetEthAddress().String() + + _, err = suite.CITS.DeliverEthTx(sender, msgEthereumTx) + suite.Require().NoError(err) + + suite.Commit() // trigger EVM Tx indexer to index block + + txByHash, err := suite.CITS.RpcBackend.GetTransactionByHash(msgEthereumTx.AsTransaction().Hash()) + suite.Require().NoError(err) + suite.Require().NotNil(txByHash, "failed to find tx by hash") + suite.Require().NotNil(txByHash.BlockNumber) + suite.Require().NotNil(txByHash.BlockHash) + + gotBlockByNumber, err := suite.GetEthPublicAPI().GetBlockByNumber(rpctypes.BlockNumber(txByHash.BlockNumber.ToInt().Int64()), true) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByNumber) + + gotBlockByHash, err := suite.GetEthPublicAPI().GetBlockByHash(*txByHash.BlockHash, true) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByHash) + + suite.Equal(gotBlockByNumber, gotBlockByHash, "result of eth_getBlockByNumber and eth_getBlockByHash must be same") + + bzGotBlockByNumber, err := json.Marshal(gotBlockByNumber) + suite.Require().NoError(err) + + var textResultStruct resultGetBlockStruct + err = json.Unmarshal(bzGotBlockByNumber, &textResultStruct) + suite.Require().NoError(err) + + suite.Require().NotNil(textResultStruct.Transactions) + suite.Require().Len(textResultStruct.Transactions, 1) + + txData, ok := textResultStruct.Transactions[0].(map[string]interface{}) + suite.True(ok) + + bz, err := json.Marshal(txData) + suite.Require().NoError(err) + + var ethRpcTxs rpctypes.RPCTransaction + err = json.Unmarshal(bz, ðRpcTxs) + suite.Require().NoError(err, "failed to unmarshal to RPCTransaction") + + if suite.NotNil(txData["from"]) { + if suite.IsType("string", txData["from"]) { + suite.Equal(strings.ToLower(sender.GetEthAddress().String()), txData["from"]) + } + } + + if suite.NotNil(txData["to"]) { + if suite.IsType("string", txData["to"]) { + suite.Equal(strings.ToLower(receiver.GetEthAddress().String()), txData["to"]) + } + } + + if suite.NotNil(txData["nonce"]) { + if suite.IsType("string", txData["nonce"]) { + suite.Equal(fmt.Sprintf("0x%x", evmTxArgs.Nonce), txData["nonce"]) + } + } + + if suite.NotNil(txData["accessList"]) { + suite.Len(txData["accessList"].([]interface{}), 2) + } + }) + + suite.Run("test input data of txs in full-txs mode", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + + evmKeeper := suite.App().EvmKeeper() + nonce := evmKeeper.GetNonce(suite.Ctx(), deployer.GetEthAddress()) + + _, evmTxsMsg, _, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Require().NoError(err) + nonce++ + + suite.Commit() // trigger EVM Tx indexer to index block + + txByHash, err := suite.CITS.RpcBackend.GetTransactionByHash(evmTxsMsg.AsTransaction().Hash()) + suite.Require().NoError(err) + suite.Require().NotNil(txByHash, "failed to find tx by hash") + suite.Require().NotNil(txByHash.BlockNumber) + suite.Require().NotNil(txByHash.BlockHash) + + gotBlockByNumber, err := suite.GetEthPublicAPI().GetBlockByNumber(rpctypes.BlockNumber(txByHash.BlockNumber.ToInt().Int64()), true) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByNumber) + + gotBlockByHash, err := suite.GetEthPublicAPI().GetBlockByHash(*txByHash.BlockHash, true) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByHash) + + suite.Equal(gotBlockByNumber, gotBlockByHash, "result of eth_getBlockByNumber and eth_getBlockByHash must be same") + + bzGotBlockByNumber, err := json.Marshal(gotBlockByNumber) + suite.Require().NoError(err) + + var textResultStruct resultGetBlockStruct + err = json.Unmarshal(bzGotBlockByNumber, &textResultStruct) + suite.Require().NoError(err) + + suite.Require().NotNil(textResultStruct.Transactions) + suite.Require().Len(textResultStruct.Transactions, 1) + + txData, ok := textResultStruct.Transactions[0].(map[string]interface{}) + suite.True(ok) + + bz, err := json.Marshal(txData) + suite.Require().NoError(err) + + var ethRpcTxs rpctypes.RPCTransaction + err = json.Unmarshal(bz, ðRpcTxs) + suite.Require().NoError(err, "failed to unmarshal to RPCTransaction") + + if suite.NotNil(txData["from"]) { + if suite.IsType("string", txData["from"]) { + suite.Equal(strings.ToLower(deployer.GetEthAddress().String()), txData["from"]) + } + } + + suite.Nil(txData["to"]) // it is contract deployment + + if suite.NotNil(txData["input"]) { + if suite.IsType("string", txData["input"]) { + suite.Equal("0x"+strings.ToLower(hex.EncodeToString(evmTxsMsg.AsTransaction().Data())), txData["input"]) + } + } + }) + + suite.Run("txs index must be unique and ordered ascending in EVM block", func() { + suite.Commit() + + receiver := integration_test_util.NewTestAccount(suite.T(), nil) + + var allSenders []*itutiltypes.TestAccount + var msgEvmTxs []*evmtypes.MsgEthereumTx + var evmTxSender []*itutiltypes.TestAccount + + for n := 1; n <= 6; n++ { + sender := integration_test_util.NewTestAccount(suite.T(), nil) + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(10)) + allSenders = append(allSenders, sender) + } + + // wait new block then send some txs to ensure all txs are included in the same block + suite.CITS.WaitNextBlockOrCommit() + + actionBlockHeight := suite.CITS.GetLatestBlockHeight() + + for i, sender := range allSenders { + // create interleaved transactions Evm => Cosmos => Evm => Cosmos => ... + + if i%2 == 0 { + // Txs must be sent async to ensure same block height + msgEthereumTx, err := suite.CITS.TxSendViaEVMAsync(sender, receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + + msgEvmTxs = append(msgEvmTxs, msgEthereumTx) + evmTxSender = append(evmTxSender, sender) + } else { + // Txs must be sent async to ensure same block height + err := suite.CITS.TxSendAsync(sender, receiver, 1) // bank sent + suite.Require().NoError(err, "failed to send tx to create test data") + } + } + + suite.CITS.WaitNextBlockOrCommit() // finalize the test block + + suite.Require().Equal(actionBlockHeight+1, suite.CITS.GetLatestBlockHeight(), "be one block later") + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + var uniqueBlockNumber int64 + + for _, sentEvmTx := range msgEvmTxs { + sentTxHash := sentEvmTx.AsTransaction().Hash() + txByHash, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(txByHash) + suite.Equal(sentTxHash, txByHash.Hash) + if suite.NotNil(txByHash.BlockHash) { + suite.Equal(1, txByHash.BlockHash.Big().Sign()) // positive + } + if suite.NotNil(txByHash.BlockNumber) { + if suite.Equal(1, txByHash.BlockNumber.ToInt().Sign()) { // positive + blockNumber := txByHash.BlockNumber.ToInt().Int64() + if uniqueBlockNumber == 0 { + uniqueBlockNumber = blockNumber + } else { + suite.Require().Equal(uniqueBlockNumber, blockNumber, "expected all test txs must be in the same block") + } + } + } + } + + gotBlockByNumber, err := suite.GetEthPublicAPI().GetBlockByNumber(rpctypes.BlockNumber(uniqueBlockNumber), true) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByNumber) + + bzGotBlockByNumber, err := json.Marshal(gotBlockByNumber) + suite.Require().NoError(err) + + var textResultStruct resultGetBlockStruct + err = json.Unmarshal(bzGotBlockByNumber, &textResultStruct) + suite.Require().NoError(err) + + gotBlockByHash, err := suite.GetEthPublicAPI().GetBlockByHash(common.HexToHash(textResultStruct.Hash), true) + suite.Require().NoError(err) + suite.Require().NotNil(gotBlockByHash) + + suite.Equal(gotBlockByNumber, gotBlockByHash, "result of eth_getBlockByNumber and eth_getBlockByHash must be same") + + suite.Require().NotNil(textResultStruct.Transactions) + suite.Require().Len(textResultStruct.Transactions, len(msgEvmTxs), "must be same as sent EVM txs") + + txIndexTracker := make([]bool, len(msgEvmTxs)) + + for _, tx := range textResultStruct.Transactions { + txData, ok := tx.(map[string]interface{}) + suite.Require().True(ok, "when full-txs mode, tx list must be the tx data itself") + + bz, err := json.Marshal(txData) + suite.Require().NoError(err) + + var ethRpcTxs rpctypes.RPCTransaction + err = json.Unmarshal(bz, ðRpcTxs) + suite.Require().NoError(err, "failed to unmarshal to RPCTransaction") + + if suite.NotNil(txData["transactionIndex"]) { + if suite.IsType("string", txData["transactionIndex"]) { + txIndex, err := strconv.ParseInt(strings.TrimPrefix(txData["transactionIndex"].(string), "0x"), 16, 64) + suite.Require().NoError(err) + + reserved := txIndexTracker[int(txIndex)] + if reserved { + suite.Failf("tx index must be unique", "tx index %d is already reserved", txIndex) + } else { + txIndexTracker[txIndex] = true + } + } + } + } + + for i, reserved := range txIndexTracker { + if !reserved { + suite.Failf("lacking tx tracker", "where is tx index %d?", i) + } + } + }) +} + +func (suite *EthRpcTestSuite) Test_GetBlockTransactionCountByNumberAndHash() { + var err error + + // shift some blocks + randomShiftingBlocksCount := int(rand.Uint32()%3 + 3) + for i := 0; i < randomShiftingBlocksCount; i++ { + suite.Commit() + } + + // prepare txs + var nonEvmTxsCount = 1 + var evmTxsCount = len(suite.CITS.WalletAccounts) - nonEvmTxsCount + var senderEvmTxs, senderNonEvmTxs []*itutiltypes.TestAccount + + // prepare senders + for _, sender := range suite.CITS.WalletAccounts { + if len(senderEvmTxs) < evmTxsCount { + senderEvmTxs = append(senderEvmTxs, sender) + } else { + senderNonEvmTxs = append(senderNonEvmTxs, sender) + } + } + + // wait new block then send some txs to ensure all txs are included in the same block + suite.CITS.WaitNextBlockOrCommit() + + testBlockHeight := suite.CITS.GetLatestBlockHeight() + + receiver := integration_test_util.NewTestAccount(suite.T(), nil) + + msgEvmTxs := make(map[string]*evmtypes.MsgEthereumTx) + + startTime := time.Now().UTC().UnixMilli() + for num := 1; num <= evmTxsCount; num++ { + // Txs must be sent async to ensure same block height + + msgEthereumTx, err := suite.CITS.TxSendViaEVMAsync(senderEvmTxs[num-1], receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + + msgEvmTxs[msgEthereumTx.Hash] = msgEthereumTx + } + + for num := 1; num <= nonEvmTxsCount; num++ { + // Txs must be sent async to ensure same block height + err = suite.CITS.TxSendAsync(senderNonEvmTxs[num-1], receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + } + fmt.Println("Broadcast takes", time.Now().UTC().UnixMilli()-startTime, "ms") + + suite.CITS.WaitNextBlockOrCommit() // finalize the test block + + if testBlockHeight+1 != suite.CITS.GetLatestBlockHeight() { + suite.T().Skip("test skipped because the expected context block number does not matches") + } + + testBlockHeight++ // since txs go to mempool and only included in the next block + + fmt.Println("testBlockHeight", testBlockHeight) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + blockResult, err := suite.CITS.QueryClients.TendermintRpcHttpClient.Block(context.Background(), ptrInt64(testBlockHeight)) + suite.Require().NoError(err) + suite.Require().NotNil(blockResult) + suite.Require().Equal(evmTxsCount+nonEvmTxsCount, len(blockResult.Block.Txs), "must be same as sent txs count for both EVM & non-EVM txs") + + gotCountByBlockNumber := suite.GetEthPublicAPI().GetBlockTransactionCountByNumber(rpctypes.BlockNumber(testBlockHeight)) + suite.Require().NotNil(gotCountByBlockNumber) + gotCountByBlockHash := suite.GetEthPublicAPI().GetBlockTransactionCountByHash(common.BytesToHash(blockResult.Block.Hash())) + suite.Require().NotNil(gotCountByBlockHash) + + if suite.Equal(uint(evmTxsCount), uint(*gotCountByBlockNumber), "must be same as sent EVM txs") { + if suite.Equal(uint(evmTxsCount), uint(*gotCountByBlockHash), "must be same as sent EVM txs") { + suite.Equal(gotCountByBlockNumber, gotCountByBlockHash, "result of eth_getBlockTransactionCountByNumber and eth_getBlockTransactionCountByHash must be same") + } + } +} diff --git a/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_transaction_test.go b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_transaction_test.go new file mode 100644 index 0000000000..877159459b --- /dev/null +++ b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/eth_api_transaction_test.go @@ -0,0 +1,862 @@ +package demo + +import ( + "encoding/json" + "fmt" + "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "math/big" + "math/rand" + "reflect" +) + +//goland:noinspection SpellCheckingInspection + +func (suite *EthRpcTestSuite) Test_GetTransactionByHash() { + suite.Run("basic", func() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + sentEvmTx, err := suite.CITS.TxSendViaEVM(sender, receiver, 1) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + sentTxHash := sentEvmTx.AsTransaction().Hash() + gotTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotTx) + suite.Equal(sentTxHash, gotTx.Hash) + if suite.NotNil(gotTx.BlockHash) { + suite.Equal(1, gotTx.BlockHash.Big().Sign()) // positive + } + if suite.NotNil(gotTx.BlockNumber) { + suite.Equal(1, gotTx.BlockNumber.ToInt().Sign()) + } + suite.Equal(sender.GetEthAddress(), gotTx.From) + suite.Equal(hexutil.Uint64(sentEvmTx.GetGas()), gotTx.Gas) + if suite.NotNil(gotTx.GasPrice) { + suite.Equal(1, gotTx.GasPrice.ToInt().Sign()) // positive + } + if suite.NotNil(gotTx.To) { + suite.Equal(receiver.GetEthAddress(), *gotTx.To) + } + suite.Empty(gotTx.Input) + suite.Equal(hexutil.Uint64(0), gotTx.Nonce) + if suite.NotNil(gotTx.TransactionIndex) { + suite.Equal(hexutil.Uint64(0), *gotTx.TransactionIndex) + } + if suite.NotNil(gotTx.Value) { + suite.Equal(suite.CITS.NewBaseCoin(1).Amount.Int64(), gotTx.Value.ToInt().Int64()) + } + suite.Equal(hexutil.Uint64(sentEvmTx.AsTransaction().Type()), gotTx.Type) + suite.Empty(gotTx.Accesses) + if suite.NotNil(gotTx.ChainID) { + suite.Equal(((*hexutil.Big)(suite.App().EvmKeeper().ChainID())).String(), gotTx.ChainID.String()) + } + v, r, s := sentEvmTx.AsTransaction().RawSignatureValues() + if suite.NotNil(gotTx.V) { + suite.Equal(hexutil.Big(*v), *gotTx.V) + } + if suite.NotNil(gotTx.R) { + suite.Equal(hexutil.Big(*r), *gotTx.R) + } + if suite.NotNil(gotTx.S) { + suite.Equal(hexutil.Big(*s), *gotTx.S) + } + }) + + suite.Run("mixed EVM & Cosmos transfer txs", func() { + receiver := integration_test_util.NewTestAccount(suite.T(), nil) + + var allSenders []*itutiltypes.TestAccount + var msgEvmTxs []*evmtypes.MsgEthereumTx + var evmTxSender []*itutiltypes.TestAccount + + for n := 1; n <= 6; n++ { + sender := integration_test_util.NewTestAccount(suite.T(), nil) + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(10)) + allSenders = append(allSenders, sender) + } + + // wait new block then send some txs to ensure all txs are included in the same block + suite.CITS.WaitNextBlockOrCommit() + + actionBlockHeight := suite.CITS.GetLatestBlockHeight() + + for i, sender := range allSenders { + // create interleaved transactions Evm => Cosmos => Evm => Cosmos => ... + + if i%2 == 0 { + // Txs must be sent async to ensure same block height + msgEthereumTx, err := suite.CITS.TxSendViaEVMAsync(sender, receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + + msgEvmTxs = append(msgEvmTxs, msgEthereumTx) + evmTxSender = append(evmTxSender, sender) + } else { + // Txs must be sent async to ensure same block height + err := suite.CITS.TxSendAsync(sender, receiver, 1) // bank sent + suite.Require().NoError(err, "failed to send tx to create test data") + } + } + + suite.CITS.WaitNextBlockOrCommit() // finalize the test block + + suite.Require().Equal(actionBlockHeight+1, suite.CITS.GetLatestBlockHeight(), "be one block later") + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + var uniqueBlockNumber int64 + txIndexTracker := make([]bool, len(msgEvmTxs)) + + for i, sentEvmTx := range msgEvmTxs { + sentTxHash := sentEvmTx.AsTransaction().Hash() + gotTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotTx) + suite.Equal(sentTxHash, gotTx.Hash) + if suite.NotNil(gotTx.BlockHash) { + suite.Equal(1, gotTx.BlockHash.Big().Sign()) // positive + } + if suite.NotNil(gotTx.BlockNumber) { + if suite.Equal(1, gotTx.BlockNumber.ToInt().Sign()) { // positive + blockNumber := gotTx.BlockNumber.ToInt().Int64() + if uniqueBlockNumber == 0 { + uniqueBlockNumber = blockNumber + } else { + suite.Require().Equal(uniqueBlockNumber, blockNumber, "expected all test txs must be in the same block") + } + } + } + suite.Equal(evmTxSender[i].GetEthAddress(), gotTx.From) + suite.Equal(hexutil.Uint64(sentEvmTx.GetGas()), gotTx.Gas) + if suite.NotNil(gotTx.GasPrice) { + suite.Equal(1, gotTx.GasPrice.ToInt().Sign()) // positive + } + if suite.NotNil(gotTx.To) { + suite.Equal(receiver.GetEthAddress(), *gotTx.To) + } + suite.Empty(gotTx.Input) + suite.Equal(hexutil.Uint64(0), gotTx.Nonce) + if suite.NotNil(gotTx.TransactionIndex) { + txIndex := int(*gotTx.TransactionIndex) + reserved := txIndexTracker[txIndex] + if reserved { + suite.Failf("tx index must be unique", "tx index %d is already reserved", txIndex) + } else { + txIndexTracker[txIndex] = true + } + } + if suite.NotNil(gotTx.Value) { + suite.Equal(suite.CITS.NewBaseCoin(1).Amount.Int64(), gotTx.Value.ToInt().Int64()) + } + suite.Equal(hexutil.Uint64(sentEvmTx.AsTransaction().Type()), gotTx.Type) + suite.Empty(gotTx.Accesses) + if suite.NotNil(gotTx.ChainID) { + suite.Equal(((*hexutil.Big)(suite.App().EvmKeeper().ChainID())).String(), gotTx.ChainID.String()) + } + v, r, s := sentEvmTx.AsTransaction().RawSignatureValues() + if suite.NotNil(gotTx.V) { + suite.Equal(hexutil.Big(*v), *gotTx.V) + } + if suite.NotNil(gotTx.R) { + suite.Equal(hexutil.Big(*r), *gotTx.R) + } + if suite.NotNil(gotTx.S) { + suite.Equal(hexutil.Big(*s), *gotTx.S) + } + } + + for i, reserved := range txIndexTracker { + if !reserved { + suite.Failf("lacking tx tracker", "where is tx index %d?", i) + } + } + }) + + suite.Run("verify a contract deployment", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + deployerNonce := suite.App().EvmKeeper().GetNonce(suite.Ctx(), deployer.GetEthAddress()) + + _, sentEvmTx, _, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + sentTxHash := sentEvmTx.AsTransaction().Hash() + gotTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotTx) + suite.Equal(sentTxHash, gotTx.Hash) + if suite.NotNil(gotTx.BlockHash) { + suite.Equal(1, gotTx.BlockHash.Big().Sign()) // positive + } + if suite.NotNil(gotTx.BlockNumber) { + suite.Equal(1, gotTx.BlockNumber.ToInt().Sign()) + } + suite.Equal(deployer.GetEthAddress(), gotTx.From) + suite.Equal(hexutil.Uint64(sentEvmTx.GetGas()), gotTx.Gas) + if suite.NotNil(gotTx.GasPrice) { + suite.Equal(1, gotTx.GasPrice.ToInt().Sign()) // positive + } + suite.Nil(gotTx.To) + suite.Equal(hexutil.Bytes(sentEvmTx.AsTransaction().Data()), gotTx.Input) + suite.Equal(hexutil.Uint64(deployerNonce), gotTx.Nonce) + if suite.NotNil(gotTx.TransactionIndex) { + suite.Equal(hexutil.Uint64(0), *gotTx.TransactionIndex) + } + if gotTx.Value != nil { + suite.Zero(gotTx.Value.ToInt().Sign()) + } + suite.Equal(hexutil.Uint64(sentEvmTx.AsTransaction().Type()), gotTx.Type) + suite.Empty(gotTx.Accesses) + if suite.NotNil(gotTx.ChainID) { + suite.Equal(((*hexutil.Big)(suite.App().EvmKeeper().ChainID())).String(), gotTx.ChainID.String()) + } + v, r, s := sentEvmTx.AsTransaction().RawSignatureValues() + if suite.NotNil(gotTx.V) { + suite.Equal(hexutil.Big(*v), *gotTx.V) + } + if suite.NotNil(gotTx.R) { + suite.Equal(hexutil.Big(*r), *gotTx.R) + } + if suite.NotNil(gotTx.S) { + suite.Equal(hexutil.Big(*s), *gotTx.S) + } + }) +} + +func (suite *EthRpcTestSuite) Test_GetTransactionCount() { + sender := suite.CITS.WalletAccounts.Number(1) + + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(100)) // prepare some coins enough for multiple txs + + for i := 0; i < int(rand.Uint32()%3+1); i++ { + suite.Commit() + } + + getBlockHash := func(height int64) common.Hash { + blockByNumber, err := suite.GetEthPublicAPI().GetBlockByNumber(rpctypes.BlockNumber(height), false) + suite.Require().NoError(err) + suite.Require().NotNil(blockByNumber) + hash, found := blockByNumber["hash"] + suite.Require().True(found) + return common.BytesToHash(hash.(hexutil.Bytes)) + } + + assertTxsCountByBlockNumber := func(account common.Address, height int64, wantTxsCount uint64) { + blockNumber := rpctypes.BlockNumber(height) + + count, err := suite.GetEthPublicAPI().GetTransactionCount(account, rpctypes.BlockNumberOrHash{ + BlockNumber: &blockNumber, + }) + suite.Require().NoError(err) + suite.Require().NotNil(count) + suite.Equalf(hexutil.Uint64(wantTxsCount), *count, "want txs count = %d at block %d but got %v, account %s", wantTxsCount, height, *count, account.String()) + } + + assertTxsCountByBlockHash := func(account common.Address, blockHash common.Hash, wantTxsCount uint64) { + count, err := suite.GetEthPublicAPI().GetTransactionCount(account, rpctypes.BlockNumberOrHash{ + BlockHash: &blockHash, + }) + suite.Require().NoError(err) + suite.Require().NotNil(count) + suite.Equalf(hexutil.Uint64(wantTxsCount), *count, "want txs count = %d at block %s but got %v, account %s", wantTxsCount, blockHash, *count, account.String()) + } + + suite.Run("fresh existing account always return 0, by block number", func() { + assertTxsCountByBlockNumber(sender.GetEthAddress(), 0, 0) + assertTxsCountByBlockNumber(sender.GetEthAddress(), suite.CITS.GetLatestBlockHeight(), 0) + }) + + suite.Run("fresh existing account always return 0, by block hash", func() { + assertTxsCountByBlockHash(sender.GetEthAddress(), getBlockHash(suite.CITS.GetLatestBlockHeight()), 0) + }) + + nonExistsAccount := integration_test_util.NewTestAccount(suite.T(), nil) + + suite.Run("non-exists account always return 0, by block number", func() { + assertTxsCountByBlockNumber(nonExistsAccount.GetEthAddress(), 0, 0) + assertTxsCountByBlockNumber(nonExistsAccount.GetEthAddress(), suite.CITS.GetLatestBlockHeight(), 0) + }) + + suite.Run("non-exists account always return 0, by block hash", func() { + assertTxsCountByBlockHash(nonExistsAccount.GetEthAddress(), getBlockHash(suite.CITS.GetLatestBlockHeight()), 0) + }) + + type blockInfo struct { + height int64 + hash common.Hash + } + + nonceTracker := make(map[uint64]blockInfo) + + for i := 0; i < int(rand.Uint32()%5)+2; i++ { + evmTx, err := suite.CITS.TxSendViaEVM(sender, nonExistsAccount, 1) + suite.Require().NoError(err) + + suite.Commit() // commit to passive trigger EVM Tx indexer + + tx, err := suite.GetEthPublicAPI().GetTransactionByHash(evmTx.AsTransaction().Hash()) + + nonceTracker[evmTx.AsTransaction().Nonce()] = blockInfo{ + height: tx.BlockNumber.ToInt().Int64(), + hash: *tx.BlockHash, + } + } + + for nonce, blockInfo := range nonceTracker { + wantTxsCount := nonce + 1 + assertTxsCountByBlockNumber(sender.GetEthAddress(), blockInfo.height, wantTxsCount) + assertTxsCountByBlockHash(sender.GetEthAddress(), blockInfo.hash, wantTxsCount) + } +} + +func (suite *EthRpcTestSuite) Test_GetTransactionReceipt() { + suite.Run("basic", func() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + sentEvmTx, err := suite.CITS.TxSendViaEVM(sender, receiver, 1) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + sentTxHash := sentEvmTx.AsTransaction().Hash() + + gotTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotTx) + + gotReceipt, err := suite.GetEthPublicAPI().GetTransactionReceipt(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotReceipt) + + bzReceipt, err := json.Marshal(gotReceipt) + suite.Require().NoError(err) + + var receipt ethtypes.Receipt + err = json.Unmarshal(bzReceipt, &receipt) + suite.Require().NoError(err) + + suite.Equal(uint64(1), receipt.Status) // success + suite.Greater(receipt.CumulativeGasUsed, uint64(0)) + if suite.NotNil(receipt.Bloom) { + suite.Len(receipt.Bloom.Bytes(), ethtypes.BloomByteLength) + } + suite.Empty(receipt.Logs) + suite.Equal(sentTxHash, receipt.TxHash) + suite.Nil(gotReceipt["contractAddress"]) + suite.Greater(receipt.GasUsed, uint64(0)) + suite.Equal(*gotTx.BlockHash, receipt.BlockHash) + suite.Equal(gotTx.BlockNumber.ToInt().Int64(), receipt.BlockNumber.Int64()) + suite.Equal(uint(*gotTx.TransactionIndex), receipt.TransactionIndex) + if suite.NotNil(gotReceipt["from"]) { + suite.Equal(sender.GetEthAddress(), gotReceipt["from"].(common.Address)) + } + if suite.NotNil(gotReceipt["to"]) { + suite.Equal(receiver.GetEthAddress(), *(gotReceipt["to"].(*common.Address))) + } + suite.Equal(sentEvmTx.AsTransaction().Type(), receipt.Type) + }) + + suite.Run("matching tx index in block mixed EVM & Cosmos transfer txs", func() { + receiver := integration_test_util.NewTestAccount(suite.T(), nil) + + var allSenders []*itutiltypes.TestAccount + var msgEvmTxs []*evmtypes.MsgEthereumTx + var evmTxSender []*itutiltypes.TestAccount + + for n := 1; n <= 6; n++ { + sender := integration_test_util.NewTestAccount(suite.T(), nil) + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(10)) + allSenders = append(allSenders, sender) + } + + // wait new block then send some txs to ensure all txs are included in the same block + suite.CITS.WaitNextBlockOrCommit() + + actionBlockHeight := suite.CITS.GetLatestBlockHeight() + + for i, sender := range allSenders { + // create interleaved transactions Evm => Cosmos => Evm => Cosmos => ... + + if i%2 == 0 { + // Txs must be sent async to ensure same block height + msgEthereumTx, err := suite.CITS.TxSendViaEVMAsync(sender, receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + + msgEvmTxs = append(msgEvmTxs, msgEthereumTx) + evmTxSender = append(evmTxSender, sender) + } else { + // Txs must be sent async to ensure same block height + err := suite.CITS.TxSendAsync(sender, receiver, 1) // bank sent + suite.Require().NoError(err, "failed to send tx to create test data") + } + } + + suite.CITS.WaitNextBlockOrCommit() // finalize the test block + + suite.Require().Equal(actionBlockHeight+1, suite.CITS.GetLatestBlockHeight(), "be one block later") + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + for _, sentEvmTx := range msgEvmTxs { + sentTxHash := sentEvmTx.AsTransaction().Hash() + + gotTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotTx) + + gotReceipt, err := suite.GetEthPublicAPI().GetTransactionReceipt(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotReceipt) + + bzReceipt, err := json.Marshal(gotReceipt) + suite.Require().NoError(err) + + var receipt ethtypes.Receipt + err = json.Unmarshal(bzReceipt, &receipt) + suite.Require().NoError(err) + + suite.Equal(uint(*gotTx.TransactionIndex), receipt.TransactionIndex) + } + }) + + suite.Run("verify a contract deployment", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + + contractAddress, sentEvmTx, _, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + sentTxHash := sentEvmTx.AsTransaction().Hash() + + gotReceipt, err := suite.GetEthPublicAPI().GetTransactionReceipt(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotReceipt) + + bzReceipt, err := json.Marshal(gotReceipt) + suite.Require().NoError(err) + + var receipt ethtypes.Receipt + err = json.Unmarshal(bzReceipt, &receipt) + suite.Require().NoError(err) + + suite.Equal(contractAddress, receipt.ContractAddress) + }) + + suite.Run("verify EVM event logs", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + + contractAddress, sentEvmTx, _, err := suite.CITS.TxDeploy5CreateFooContract(deployer) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + sentTxHash := sentEvmTx.AsTransaction().Hash() + + gotReceipt, err := suite.GetEthPublicAPI().GetTransactionReceipt(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(gotReceipt) + + bzReceipt, err := json.Marshal(gotReceipt) + suite.Require().NoError(err) + + var receipt ethtypes.Receipt + err = json.Unmarshal(bzReceipt, &receipt) + suite.Require().NoError(err) + + suite.Equal(contractAddress, receipt.ContractAddress) + if suite.Len(receipt.Logs, 1) { + log := receipt.Logs[0] + suite.Equal(contractAddress, log.Address) + suite.Len(log.Topics, 1) + suite.Equal(crypto.Keccak256([]byte("ConstructorCall()")), log.Topics[0].Bytes()) // always have at least one topic + suite.Empty(log.Data) + } + }) +} + +func (suite *EthRpcTestSuite) Test_GetTransactionByBlockNumberAndHashAndIndex() { + fetchAndCompareWithGetTransactionByHash := func(rpcTx *rpctypes.RPCTransaction) { + blockNumber := rpctypes.BlockNumber(rpcTx.BlockNumber.ToInt().Int64()) + blockHash := *rpcTx.BlockHash + + gotTxByBlockNumberAndIdx, err := suite.GetEthPublicAPI().GetTransactionByBlockNumberAndIndex(blockNumber, hexutil.Uint(*rpcTx.TransactionIndex)) + suite.Require().NoError(err) + suite.Require().NotNil(gotTxByBlockNumberAndIdx) + + gotTxByBlockHashAndIdx, err := suite.GetEthPublicAPI().GetTransactionByBlockHashAndIndex(blockHash, hexutil.Uint(*rpcTx.TransactionIndex)) + suite.Require().NoError(err) + suite.Require().NotNil(gotTxByBlockHashAndIdx) + + if !suite.True(reflect.DeepEqual(rpcTx, gotTxByBlockNumberAndIdx), "result by eth_getTransactionByBlockNumberAndIndex must be equal to eth_getTransactionByHash") { + fmt.Println("Expected:", rpcTx) + fmt.Println("Got:", gotTxByBlockNumberAndIdx) + } + if !suite.True(reflect.DeepEqual(rpcTx, gotTxByBlockHashAndIdx), "result by eth_getTransactionByBlockHashAndIndex must be equal to eth_getTransactionByHash") { + fmt.Println("Expected:", rpcTx) + fmt.Println("Got:", gotTxByBlockHashAndIdx) + } + } + + suite.Run("basic", func() { + sender := suite.CITS.WalletAccounts.Number(1) + receiver := suite.CITS.WalletAccounts.Number(2) + + sentEvmTx, err := suite.CITS.TxSendViaEVM(sender, receiver, 1) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + sentTxHash := sentEvmTx.AsTransaction().Hash() + rpcTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(rpcTx) + suite.Equal(sentTxHash, rpcTx.Hash) + + fetchAndCompareWithGetTransactionByHash(rpcTx) + }) + + suite.Run("mixed EVM & Cosmos transfer txs", func() { + receiver := integration_test_util.NewTestAccount(suite.T(), nil) + + var allSenders []*itutiltypes.TestAccount + var msgEvmTxs []*evmtypes.MsgEthereumTx + var evmTxSender []*itutiltypes.TestAccount + + for n := 1; n <= 6; n++ { + sender := integration_test_util.NewTestAccount(suite.T(), nil) + suite.CITS.MintCoin(sender, suite.CITS.NewBaseCoin(10)) + allSenders = append(allSenders, sender) + } + + // wait new block then send some txs to ensure all txs are included in the same block + suite.CITS.WaitNextBlockOrCommit() + + actionBlockHeight := suite.CITS.GetLatestBlockHeight() + + for i, sender := range allSenders { + // create interleaved transactions Evm => Cosmos => Evm => Cosmos => ... + + if i%2 == 0 { + // Txs must be sent async to ensure same block height + msgEthereumTx, err := suite.CITS.TxSendViaEVMAsync(sender, receiver, 1) + suite.Require().NoError(err, "failed to send tx to create test data") + + msgEvmTxs = append(msgEvmTxs, msgEthereumTx) + evmTxSender = append(evmTxSender, sender) + } else { + // Txs must be sent async to ensure same block height + err := suite.CITS.TxSendAsync(sender, receiver, 1) // bank sent + suite.Require().NoError(err, "failed to send tx to create test data") + } + } + + suite.CITS.WaitNextBlockOrCommit() // finalize the test block + + suite.Require().Equal(actionBlockHeight+1, suite.CITS.GetLatestBlockHeight(), "be one block later") + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + balance := suite.CITS.QueryBalance(0, receiver.GetCosmosAddress().String()) + suite.Require().False(balance.IsZero(), "receiver must received some balance") + + for _, sentEvmTx := range msgEvmTxs { + sentTxHash := sentEvmTx.AsTransaction().Hash() + rpcTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(rpcTx) + suite.Equal(sentTxHash, rpcTx.Hash) + + fetchAndCompareWithGetTransactionByHash(rpcTx) + } + }) + + suite.Run("verify a contract deployment", func() { + deployer := suite.CITS.WalletAccounts.Number(1) + + _, sentEvmTx, _, err := suite.CITS.TxDeploy1StorageContract(deployer) + suite.Require().NoError(err) + + suite.CITS.Commit() // commit to passive trigger EVM Tx indexer + + sentTxHash := sentEvmTx.AsTransaction().Hash() + rpcTx, err := suite.GetEthPublicAPI().GetTransactionByHash(sentTxHash) + suite.Require().NoError(err) + suite.Require().NotNil(rpcTx) + suite.Equal(sentTxHash, rpcTx.Hash) + + fetchAndCompareWithGetTransactionByHash(rpcTx) + }) +} + +func (suite *EthRpcTestSuite) Test_SendRawTransaction() { + receiver := integration_test_util.NewTestAccount(suite.T(), nil) + + // define + + txConfig := suite.CITS.QueryClients.ClientQueryCtx.TxConfig + txBuilder := txConfig.NewTxBuilder() + txEncoder := txConfig.TxEncoder() + + // helper methods + + newMsgEthTx := func(sender *itutiltypes.TestAccount) *evmtypes.MsgEthereumTx { + to := receiver.GetEthAddress() + + gasPrice := suite.App().FeeMarketKeeper().GetBaseFee(suite.Ctx()) + evmTxArgs := &evmtypes.EvmTxArgs{ + ChainID: suite.App().EvmKeeper().ChainID(), + Nonce: suite.App().EvmKeeper().GetNonce(suite.Ctx(), sender.GetEthAddress()), + GasLimit: 21000, + Input: nil, + To: &to, + Amount: big.NewInt(1), + GasFeeCap: gasPrice, + GasPrice: gasPrice, + GasTipCap: big.NewInt(1), + Accesses: nil, + } + + msgEvmTx := evmtypes.NewTx(evmTxArgs) + msgEvmTx.From = sender.GetEthAddress().String() + + return msgEvmTx + } + + newSignedEthTx := func(sender *itutiltypes.TestAccount) *ethtypes.Transaction { + msgEvmTx := newMsgEthTx(sender) + + ethTx := msgEvmTx.AsTransaction() + sig, _, err := sender.Signer.SignByAddress(msgEvmTx.GetFrom(), suite.CITS.EthSigner.Hash(ethTx).Bytes()) + suite.Require().NoError(err) + + signedEthTx, err := ethTx.WithSignature(suite.CITS.EthSigner, sig) + suite.Require().NoError(err) + + return signedEthTx + } + + // signed tx + + senderForSignedEthTx := suite.CITS.WalletAccounts.Number(1) + signedEthTx := newSignedEthTx(senderForSignedEthTx) + signedRlpBz, err := rlp.EncodeToBytes(signedEthTx) + suite.Require().NoError(err) + + senderForToBeSignedMsgEthTx := suite.CITS.WalletAccounts.Number(2) + toBeSignedMsgEthTx := newMsgEthTx(senderForToBeSignedMsgEthTx) + signedCosmosMsgEthTx, err := suite.CITS.PrepareEthTx(senderForToBeSignedMsgEthTx, toBeSignedMsgEthTx) + suite.Require().NoError(err) + bzSignedCosmosMsgEthTx, err := txEncoder(signedCosmosMsgEthTx) + suite.Require().NoError(err) + + // non-signed tx + + senderForNonSignedMsgEthTx := suite.CITS.WalletAccounts.Number(3) + nonSignedMsgEthTx := newMsgEthTx(senderForNonSignedMsgEthTx) + nonSignedEthTx := nonSignedMsgEthTx.AsTransaction() + notSignedRlpBz, err := rlp.EncodeToBytes(nonSignedEthTx) + suite.Require().NoError(err) + + err = txBuilder.SetMsgs(nonSignedMsgEthTx) + suite.Require().NoError(err) + + nonSignedTxEncodedBz, err := txEncoder(txBuilder.GetTx()) + suite.Require().NoError(err) + + // begin test + + testCases := []struct { + name string + rawTx []byte + sourceTxHash common.Hash + expPass bool + expErrContains string + }{ + { + name: "send signed tx", + rawTx: signedRlpBz, + sourceTxHash: signedEthTx.Hash(), + expPass: true, + }, + { + name: "not accept Cosmos tx, even tho signed", + rawTx: bzSignedCosmosMsgEthTx, + sourceTxHash: signedEthTx.Hash(), + expPass: false, + expErrContains: "transaction type not supported", + }, + { + name: "send non-signed tx", + rawTx: notSignedRlpBz, + sourceTxHash: nonSignedEthTx.Hash(), + expPass: false, + expErrContains: "only replay-protected (EIP-155) transactions allowed over RPC", + }, + { + name: "fail - empty bytes", + rawTx: []byte{}, + sourceTxHash: common.Hash{}, + expPass: false, + expErrContains: "typed transaction too short", + }, + { + name: "fail - no RLP encoded bytes", + rawTx: nonSignedTxEncodedBz, + sourceTxHash: nonSignedMsgEthTx.AsTransaction().Hash(), + expPass: false, + expErrContains: "transaction type not supported", + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + hash, err := suite.GetEthPublicAPI().SendRawTransaction(tc.rawTx) + + if tc.expPass { + suite.Require().NoError(err) + if !suite.Equal(tc.sourceTxHash, hash) { + return + } + } else { + suite.Require().Error(err) + suite.Require().NotEmptyf(tc.expErrContains, "missing expected error to check against: %s", err.Error()) + suite.Require().Contains(err.Error(), tc.expErrContains) + suite.Require().Equal(common.Hash{}, hash) + + if tc.sourceTxHash == ([32]byte{}) { // empty + // ignore later tests + return + } + } + + // wait to check if included in blocks or not + suite.Commit() + suite.Commit() + + rpcTx, err := suite.GetEthPublicAPI().GetTransactionByHash(hash) + if tc.expPass { + if suite.NotNil(rpcTx) { + suite.Equal(hash, rpcTx.Hash) + } + } else { + suite.Nil(rpcTx) + } + }) + } +} + +func (suite *EthRpcTestSuite) Test_SendTransaction() { + toAddr := suite.CITS.WalletAccounts.Number(1).GetEthAddress() + + gasPrice := suite.App().FeeMarketKeeper().GetBaseFee(suite.Ctx()) + gas := uint64(21000) + + prepareTransactionArgs := func(fromAddr common.Address) evmtypes.TransactionArgs { + nonce := hexutil.Uint64(suite.App().EvmKeeper().GetNonce(suite.Ctx(), fromAddr)) + + return evmtypes.TransactionArgs{ + From: &fromAddr, + To: &toAddr, + Gas: (*hexutil.Uint64)(&gas), + GasPrice: (*hexutil.Big)(gasPrice), + Value: (*hexutil.Big)(big.NewInt(1)), + Nonce: &nonce, + Data: nil, + Input: nil, + AccessList: nil, + ChainID: (*hexutil.Big)(suite.App().EvmKeeper().ChainID()), + } + } + + tests := []struct { + name string + preRun func() + fromAddr common.Address + expPass bool + expErrMsgContains string + }{ + { + name: "keyring not enabled", + fromAddr: suite.CITS.WalletAccounts.Number(2).GetEthAddress(), + expPass: false, + expErrMsgContains: "no key for given address or file", + }, + { + name: "keyring enabled, use account supplied in keyring", + preRun: func() { + suite.CITS.UseKeyring() + suite.Commit() // refresh rpc backend + }, + fromAddr: suite.CITS.WalletAccounts.Number(3).GetEthAddress(), + expPass: true, + }, + { + name: "keyring enabled, use random account", + preRun: func() { + suite.CITS.UseKeyring() + suite.Commit() // refresh rpc backend + }, + fromAddr: integration_test_util.NewTestAccount(suite.T(), nil).GetEthAddress(), + expPass: false, + expErrMsgContains: "no key for given address or file", + }, + } + for _, tt := range tests { + suite.Run(tt.name, func() { + if tt.preRun != nil { + tt.preRun() + } + + txHash, err := suite.GetEthPublicAPI().SendTransaction(prepareTransactionArgs(tt.fromAddr)) + + if tt.expPass { + suite.Require().NoError(err) + suite.NotEqual(common.Hash{}, txHash) + } else { + suite.Require().Error(err) + suite.Equal(common.Hash{}, txHash) + + if suite.NotEmpty(tt.expErrMsgContains, "error message must be set for fail testcase") { + suite.Contains(err.Error(), tt.expErrMsgContains) + } + + return + } + + suite.Commit() + suite.Commit() + + rpcTx, err := suite.GetEthPublicAPI().GetTransactionByHash(txHash) + if suite.NotNil(rpcTx) { + suite.Equal(txHash, rpcTx.Hash) + } + }) + } +} diff --git a/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/setup_eth_rpc_integration_test.go b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/setup_eth_rpc_integration_test.go new file mode 100644 index 0000000000..a2793f4fe9 --- /dev/null +++ b/rpc/namespaces/ethereum/eth/eth_rpc_it_suite/setup_eth_rpc_integration_test.go @@ -0,0 +1,79 @@ +package demo + +//goland:noinspection SpellCheckingInspection +import ( + "encoding/json" + "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + "github.com/EscanBE/evermint/v12/rpc/namespaces/ethereum/eth" + "github.com/cometbft/cometbft/libs/log" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/suite" + "testing" +) + +//goland:noinspection GoSnakeCaseUsage,SpellCheckingInspection +type EthRpcTestSuite struct { + suite.Suite + CITS *integration_test_util.ChainIntegrationTestSuite +} + +func (suite *EthRpcTestSuite) App() itutiltypes.ChainApp { + return suite.CITS.ChainApp +} + +func (suite *EthRpcTestSuite) Ctx() sdk.Context { + return suite.CITS.CurrentContext +} + +func (suite *EthRpcTestSuite) Commit() { + suite.CITS.Commit() +} + +func TestEthRpcTestSuite(t *testing.T) { + suite.Run(t, new(EthRpcTestSuite)) +} + +func (suite *EthRpcTestSuite) SetupSuite() { +} + +func (suite *EthRpcTestSuite) SetupTest() { + suite.CITS = integration_test_util.CreateChainIntegrationTestSuite(suite.T(), suite.Require()) + suite.CITS.EnsureTendermint() // RPC requires Tendermint +} + +func (suite *EthRpcTestSuite) TearDownTest() { + suite.CITS.Cleanup() +} + +func (suite *EthRpcTestSuite) TearDownSuite() { +} + +func (suite *EthRpcTestSuite) GetEthPublicAPI() *eth.PublicAPI { + return eth.NewPublicAPI(log.NewNopLogger(), suite.CITS.RpcBackendAt(0)) +} + +func (suite *EthRpcTestSuite) GetEthPublicAPIAt(height int64) *eth.PublicAPI { + return eth.NewPublicAPI(log.NewNopLogger(), suite.CITS.RpcBackendAt(height)) +} + +func (suite *EthRpcTestSuite) GetTxReceipt(txHash common.Hash) *ethtypes.Receipt { + mapReceipt, err := suite.CITS.RpcBackend.GetTransactionReceipt(txHash) + suite.Require().NoError(err) + suite.Require().NotNil(mapReceipt) + + bzMapReceipt, err := json.Marshal(mapReceipt) + suite.Require().NoError(err) + + var receipt ethtypes.Receipt + err = json.Unmarshal(bzMapReceipt, &receipt) + suite.Require().NoError(err) + + return &receipt +} + +func ptrInt64(num int64) *int64 { + return &num +} From c280574e27c9da8ae57a1eb36eea0c528bab1f54 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Thu, 11 Jan 2024 23:29:33 +0700 Subject: [PATCH 3/3] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9917af4b0b..982776b855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - (store) [#12](https://github.com/EscanBE/evermint/pull/12) Add local `snapshots` management commands - (store) [#14](https://github.com/EscanBE/evermint/pull/14) Add `inspect` command and sub-commands +- (test+rpc) [#74](https://github.com/EscanBE/evermint/pull/74) Add integration test util + add IT skeleton for Json-RPC ### Improvement