From 3366b3b3e32d318724e7c392599241c5723fd7a5 Mon Sep 17 00:00:00 2001 From: Robert Pirtle Date: Thu, 6 Apr 2023 10:51:13 -0700 Subject: [PATCH] feat: e2e test eip712 signing & erc20 interactions (#1535) * add helpers & tests for erc20 eth_call query & transfer * make encoding config public * add evm client & raw evm signer to account * test eip712 signing and broadcast * update for cosmos v46 * update kvtool * temporarily disable ibc tests & skip shutdown * disable all but eip712 test and massively simplify * add EIP712 tx builder & setup basic MsgSend test * reenable all tests * add eip712 test that deposits erc20 into earn * update kvtool to master branch --- tests/e2e/.env | 5 + tests/e2e/e2e_evm_contracts_test.go | 138 ++++++++++++++++++++++++++++ tests/e2e/kvtool | 2 +- tests/e2e/testutil/account.go | 11 ++- tests/e2e/testutil/chain.go | 12 ++- tests/e2e/testutil/config.go | 4 + tests/e2e/testutil/eip712.go | 107 +++++++++++++++++++++ tests/e2e/testutil/init_evm.go | 45 ++++++++- tests/e2e/testutil/suite.go | 11 ++- tests/util/erc20.go | 40 ++++++++ tests/util/sdksigner.go | 39 ++++++++ 11 files changed, 406 insertions(+), 8 deletions(-) create mode 100644 tests/e2e/testutil/eip712.go create mode 100644 tests/util/erc20.go diff --git a/tests/e2e/.env b/tests/e2e/.env index 545d3673..d89c606b 100644 --- a/tests/e2e/.env +++ b/tests/e2e/.env @@ -1,4 +1,5 @@ # E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC is for a funded account used to intialize all new testing accounts. +# Should be funded with KAVA and have an ERC20 balance E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC='tent fitness boat among census primary pipe nose dream glance cave turtle electric fabric jacket shaft easy myself genuine this sibling pulse word unfold' # E2E_KVTOOL_KAVA_CONFIG_TEMPLATE is the kvtool template used to start the chain. See the `kava.configTemplate` flag in kvtool. @@ -22,3 +23,7 @@ E2E_KAVA_UPGRADE_NAME= E2E_KAVA_UPGRADE_HEIGHT= # E2E_KAVA_UPGRADE_BASE_IMAGE_TAG is the tag of the docker image the chain should upgrade from. E2E_KAVA_UPGRADE_BASE_IMAGE_TAG= + +# E2E_KAVA_ERC20_ADDRESS is the address of a pre-deployed ERC20 token. +# The E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC account should have a balance. +E2E_KAVA_ERC20_ADDRESS=0xeA7100edA2f805356291B0E55DaD448599a72C6d diff --git a/tests/e2e/e2e_evm_contracts_test.go b/tests/e2e/e2e_evm_contracts_test.go index a6a652eb..36296d83 100644 --- a/tests/e2e/e2e_evm_contracts_test.go +++ b/tests/e2e/e2e_evm_contracts_test.go @@ -1,9 +1,18 @@ package e2e_test import ( + "context" + "math/big" "time" + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/kava-labs/kava/app" + earntypes "github.com/kava-labs/kava/x/earn/types" + evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" "github.com/kava-labs/kava/tests/e2e/contracts/greeter" "github.com/kava-labs/kava/tests/util" @@ -35,3 +44,132 @@ func (suite *IntegrationTestSuite) TestEthCallToGreeterContract() { suite.Equal("what's up!", beforeGreeting) suite.Equal(updatedGreeting, afterGreeting) } + +func (suite *IntegrationTestSuite) TestEthCallToErc20() { + randoReceiver := util.SdkToEvmAddress(app.RandomAddress()) + amount := big.NewInt(1e6) + + // make unauthenticated eth_call query to check balance + beforeBalance := suite.GetErc20Balance(randoReceiver) + + // make authenticate eth_call to transfer tokens + res := suite.FundKavaErc20Balance(randoReceiver, amount) + suite.NoError(res.Err) + + // make another unauthenticated eth_call query to check new balance + afterBalance := suite.GetErc20Balance(randoReceiver) + + suite.BigIntsEqual(big.NewInt(0), beforeBalance, "expected before balance to be zero") + suite.BigIntsEqual(amount, afterBalance, "unexpected post-transfer balance") +} + +func (suite *IntegrationTestSuite) TestEip712BasicMessageAuthorization() { + // create new funded account + sender := suite.Kava.NewFundedAccount("eip712-msgSend", sdk.NewCoins(ukava(10e6))) + receiver := app.RandomAddress() + + // setup message for sending 1KAVA to random receiver + msgs := []sdk.Msg{ + banktypes.NewMsgSend(sender.SdkAddress, receiver, sdk.NewCoins(ukava(1e6))), + } + + // create tx + tx := suite.NewEip712TxBuilder( + sender, + suite.Kava, + 1e6, + sdk.NewCoins(ukava(1e4)), + msgs, + "this is a memo", + ).GetTx() + + txBytes, err := suite.Kava.EncodingConfig.TxConfig.TxEncoder()(tx) + suite.NoError(err) + + // broadcast tx + res, err := suite.Kava.Tx.BroadcastTx(context.Background(), &txtypes.BroadcastTxRequest{ + TxBytes: txBytes, + Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, + }) + suite.NoError(err) + suite.Equal(sdkerrors.SuccessABCICode, res.TxResponse.Code) + + _, err = util.WaitForSdkTxCommit(suite.Kava.Tx, res.TxResponse.TxHash, 6*time.Second) + suite.NoError(err) + + // check that the message was processed & the kava is transferred. + balRes, err := suite.Kava.Bank.Balance(context.Background(), &banktypes.QueryBalanceRequest{ + Address: receiver.String(), + Denom: "ukava", + }) + suite.NoError(err) + suite.Equal(sdk.NewInt(1e6), balRes.Balance.Amount) +} + +// Note that this test works because the deployed erc20 is configured in evmutil & earn params. +func (suite *IntegrationTestSuite) TestEip712ConvertToCoinAndDepositToEarn() { + amount := sdk.NewInt(10e6) // 10 USDC + sdkDenom := "erc20/multichain/usdc" + + // create new funded account + depositor := suite.Kava.NewFundedAccount("eip712-earn-depositor", sdk.NewCoins(ukava(1e6))) + // give them erc20 balance to deposit + fundRes := suite.FundKavaErc20Balance(depositor.EvmAddress, amount.BigInt()) + suite.NoError(fundRes.Err) + + // setup messages for convert to coin & deposit into earn + convertMsg := evmutiltypes.NewMsgConvertERC20ToCoin( + evmutiltypes.NewInternalEVMAddress(depositor.EvmAddress), + depositor.SdkAddress, + evmutiltypes.NewInternalEVMAddress(suite.DeployedErc20Address), + amount, + ) + depositMsg := earntypes.NewMsgDeposit( + depositor.SdkAddress.String(), + sdk.NewCoin(sdkDenom, amount), + earntypes.STRATEGY_TYPE_SAVINGS, + ) + msgs := []sdk.Msg{ + // convert to coin + &convertMsg, + // deposit into earn + depositMsg, + } + + // create tx + tx := suite.NewEip712TxBuilder( + depositor, + suite.Kava, + 1e6, + sdk.NewCoins(ukava(1e4)), + msgs, + "depositing my USDC into Earn!", + ).GetTx() + + txBytes, err := suite.Kava.EncodingConfig.TxConfig.TxEncoder()(tx) + suite.NoError(err) + + // broadcast tx + res, err := suite.Kava.Tx.BroadcastTx(context.Background(), &txtypes.BroadcastTxRequest{ + TxBytes: txBytes, + Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, + }) + suite.NoError(err) + suite.Equal(sdkerrors.SuccessABCICode, res.TxResponse.Code) + + _, err = util.WaitForSdkTxCommit(suite.Kava.Tx, res.TxResponse.TxHash, 6*time.Second) + suite.NoError(err) + + // check that depositor no longer has erc20 balance + balance := suite.GetErc20Balance(depositor.EvmAddress) + suite.BigIntsEqual(big.NewInt(0), balance, "expected no erc20 balance") + + // check that account has an earn deposit position + earnRes, err := suite.Kava.Earn.Deposits(context.Background(), &earntypes.QueryDepositsRequest{ + Depositor: depositor.SdkAddress.String(), + Denom: sdkDenom, + }) + suite.NoError(err) + suite.Len(earnRes.Deposits, 1) + suite.Equal(sdk.NewDecFromInt(amount), earnRes.Deposits[0].Shares.AmountOf(sdkDenom)) +} diff --git a/tests/e2e/kvtool b/tests/e2e/kvtool index 6022e137..91110876 160000 --- a/tests/e2e/kvtool +++ b/tests/e2e/kvtool @@ -1 +1 @@ -Subproject commit 6022e137686e6ab648174b3487c3ee82ec7056ae +Subproject commit 9111087667b4ec136a4dd684762e41515a240a0f diff --git a/tests/e2e/testutil/account.go b/tests/e2e/testutil/account.go index e2085cfb..6a180436 100644 --- a/tests/e2e/testutil/account.go +++ b/tests/e2e/testutil/account.go @@ -9,6 +9,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/go-bip39" @@ -17,6 +18,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/evmos/ethermint/crypto/ethsecp256k1" + emtests "github.com/evmos/ethermint/tests" emtypes "github.com/evmos/ethermint/types" "github.com/stretchr/testify/require" @@ -28,6 +30,7 @@ type SigningAccount struct { name string mnemonic string + evmPrivKey *ethsecp256k1.PrivKey evmSigner *util.EvmSigner evmReqChan chan<- util.EvmTxRequest evmResChan <-chan util.EvmTxResponse @@ -66,7 +69,7 @@ func (chain *Chain) AddNewSigningAccount(name string, hdPath *hd.BIP44Params, ch kavaSigner := util.NewKavaSigner( chainId, - chain.encodingConfig, + chain.EncodingConfig, chain.Auth, chain.Tx, privKey, @@ -100,6 +103,7 @@ func (chain *Chain) AddNewSigningAccount(name string, hdPath *hd.BIP44Params, ch mnemonic: mnemonic, l: logger, + evmPrivKey: privKey, evmSigner: evmSigner, evmReqChan: evmReqChan, evmResChan: evmResChan, @@ -168,6 +172,11 @@ func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxRespo return response } +func (a *SigningAccount) SignRawEvmData(msg []byte) ([]byte, types.PubKey, error) { + keyringSigner := emtests.NewSigner(a.evmPrivKey) + return keyringSigner.SignByAddress(a.SdkAddress, msg) +} + // NewFundedAccount creates a SigningAccount for a random account & funds the account from the whale. func (chain *Chain) NewFundedAccount(name string, funds sdk.Coins) *SigningAccount { entropy, err := bip39.NewEntropy(128) diff --git a/tests/e2e/testutil/chain.go b/tests/e2e/testutil/chain.go index 1dcf0fcf..d1d8f453 100644 --- a/tests/e2e/testutil/chain.go +++ b/tests/e2e/testutil/chain.go @@ -13,6 +13,7 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/stretchr/testify/require" "github.com/kava-labs/kava/app" @@ -25,9 +26,8 @@ import ( // Chain wraps query clients & accounts for a network type Chain struct { - encodingConfig kavaparams.EncodingConfig - accounts map[string]*SigningAccount - t *testing.T + accounts map[string]*SigningAccount + t *testing.T StakingDenom string ChainId string @@ -35,10 +35,13 @@ type Chain struct { EvmClient *ethclient.Client ContractAddrs map[string]common.Address + EncodingConfig kavaparams.EncodingConfig + Auth authtypes.QueryClient Bank banktypes.QueryClient Community communitytypes.QueryClient Earn earntypes.QueryClient + Evm evmtypes.QueryClient Tm tmservice.ServiceClient Tx txtypes.ServiceClient } @@ -53,7 +56,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic ChainId: details.ChainId, ContractAddrs: make(map[string]common.Address), } - chain.encodingConfig = app.MakeEncodingConfig() + chain.EncodingConfig = app.MakeEncodingConfig() grpcUrl := fmt.Sprintf("http://localhost:%s", details.GrpcPort) grpcConn, err := util.NewGrpcConnection(grpcUrl) @@ -71,6 +74,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic chain.Bank = banktypes.NewQueryClient(grpcConn) chain.Community = communitytypes.NewQueryClient(grpcConn) chain.Earn = earntypes.NewQueryClient(grpcConn) + chain.Evm = evmtypes.NewQueryClient(grpcConn) chain.Tm = tmservice.NewServiceClient(grpcConn) chain.Tx = txtypes.NewServiceClient(grpcConn) diff --git a/tests/e2e/testutil/config.go b/tests/e2e/testutil/config.go index fc384ff7..ce7a82f7 100644 --- a/tests/e2e/testutil/config.go +++ b/tests/e2e/testutil/config.go @@ -31,6 +31,9 @@ type SuiteConfig struct { // Tag of kava docker image that will be upgraded to the current image before tests are run, if upgrade is enabled. KavaUpgradeBaseImageTag string + // The contract address of a deployed ERC-20 token + KavaErc20Address string + // When true, the chains will remain running after tests complete (pass or fail) SkipShutdown bool } @@ -41,6 +44,7 @@ func ParseSuiteConfig() SuiteConfig { // new accounts created during tests. it will be available under Accounts["whale"] FundedAccountMnemonic: nonemptyStringEnv("E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC"), KavaConfigTemplate: nonemptyStringEnv("E2E_KVTOOL_KAVA_CONFIG_TEMPLATE"), + KavaErc20Address: nonemptyStringEnv("E2E_KAVA_ERC20_ADDRESS"), IncludeIbcTests: mustParseBool("E2E_INCLUDE_IBC_TESTS"), IncludeAutomatedUpgrade: mustParseBool("E2E_INCLUDE_AUTOMATED_UPGRADE"), } diff --git a/tests/e2e/testutil/eip712.go b/tests/e2e/testutil/eip712.go new file mode 100644 index 00000000..1dd8f128 --- /dev/null +++ b/tests/e2e/testutil/eip712.go @@ -0,0 +1,107 @@ +package testutil + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/ethermint/ethereum/eip712" + emtypes "github.com/evmos/ethermint/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +func (suite *E2eTestSuite) NewEip712TxBuilder( + acc *SigningAccount, chain *Chain, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg, memo string, +) client.TxBuilder { + // get account details + var accDetails authtypes.AccountI + a, err := chain.Auth.Account(context.Background(), &authtypes.QueryAccountRequest{ + Address: acc.SdkAddress.String(), + }) + suite.NoError(err) + err = chain.EncodingConfig.InterfaceRegistry.UnpackAny(a.Account, &accDetails) + suite.NoError(err) + + // get nonce & acc number + nonce := accDetails.GetSequence() + accNumber := accDetails.GetAccountNumber() + + // get chain id + pc, err := emtypes.ParseChainID(chain.ChainId) + suite.NoError(err) + ethChainId := pc.Uint64() + + evmParams, err := chain.Evm.Params(context.Background(), &evmtypes.QueryParamsRequest{}) + suite.NoError(err) + + fee := legacytx.NewStdFee(gas, gasAmount) + + // build EIP712 tx + // -- untyped data + untypedData := eip712.ConstructUntypedEIP712Data( + chain.ChainId, + accNumber, + nonce, + 0, // no timeout + fee, + msgs, + memo, + nil, + ) + // -- typed data + typedData, err := eip712.WrapTxToTypedData(ethChainId, msgs, untypedData, &eip712.FeeDelegationOptions{ + FeePayer: acc.SdkAddress, + }, evmParams.Params) + suite.NoError(err) + + // -- raw data hash! + data, err := eip712.ComputeTypedDataHash(typedData) + suite.NoError(err) + + // -- sign the hash + signature, pubKey, err := acc.SignRawEvmData(data) + suite.NoError(err) + signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + + // add ExtensionOptionsWeb3Tx extension + var option *codectypes.Any + option, err = codectypes.NewAnyWithValue(&emtypes.ExtensionOptionsWeb3Tx{ + FeePayer: acc.SdkAddress.String(), + TypedDataChainID: ethChainId, + FeePayerSig: signature, + }) + suite.NoError(err) + + // create cosmos sdk tx builder + txBuilder := chain.EncodingConfig.TxConfig.NewTxBuilder() + builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) + suite.True(ok) + + builder.SetExtensionOptions(option) + builder.SetFeeAmount(fee.Amount) + builder.SetGasLimit(fee.Gas) + + sigsV2 := signing.SignatureV2{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + }, + Sequence: nonce, + } + + err = builder.SetSignatures(sigsV2) + suite.Require().NoError(err) + + err = builder.SetMsgs(msgs...) + suite.Require().NoError(err) + + builder.SetMemo(memo) + + return builder +} diff --git a/tests/e2e/testutil/init_evm.go b/tests/e2e/testutil/init_evm.go index 5210eb01..1658332c 100644 --- a/tests/e2e/testutil/init_evm.go +++ b/tests/e2e/testutil/init_evm.go @@ -1,12 +1,29 @@ package testutil -import "github.com/kava-labs/kava/tests/e2e/contracts/greeter" +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/kava-labs/kava/tests/e2e/contracts/greeter" + "github.com/kava-labs/kava/tests/util" +) // InitKavaEvmData is run after the chain is running, but before the tests are run. // It is used to initialize some EVM state, such as deploying contracts. func (suite *E2eTestSuite) InitKavaEvmData() { whale := suite.Kava.GetAccount(FundedAccountName) + // ensure funded account has nonzero erc20 balance + balance := suite.GetErc20Balance(whale.EvmAddress) + if balance.Cmp(big.NewInt(0)) != 1 { + panic(fmt.Sprintf("expected funded account (%s) to have erc20 balance", whale.EvmAddress.Hex())) + } + // deploy an example contract greeterAddr, _, _, err := greeter.DeployGreeter( whale.evmSigner.Auth, @@ -16,3 +33,29 @@ func (suite *E2eTestSuite) InitKavaEvmData() { suite.NoError(err, "failed to deploy a contract to the EVM") suite.Kava.ContractAddrs["greeter"] = greeterAddr } + +func (suite *E2eTestSuite) FundKavaErc20Balance(toAddress common.Address, amount *big.Int) EvmTxResponse { + // funded account should have erc20 balance + whale := suite.Kava.GetAccount(FundedAccountName) + + data := util.BuildErc20TransferCallData(whale.EvmAddress, toAddress, amount) + nonce, err := suite.Kava.EvmClient.PendingNonceAt(context.Background(), whale.EvmAddress) + suite.NoError(err) + + req := util.EvmTxRequest{ + Tx: ethtypes.NewTransaction(nonce, suite.DeployedErc20Address, big.NewInt(0), 1e5, big.NewInt(1e10), data), + Data: fmt.Sprintf("fund %s with ERC20 balance (%s)", toAddress.Hex(), amount.String()), + } + + return whale.SignAndBroadcastEvmTx(req) +} + +func (suite *E2eTestSuite) GetErc20Balance(address common.Address) *big.Int { + resData, err := suite.Kava.EvmClient.CallContract(context.Background(), ethereum.CallMsg{ + To: &suite.DeployedErc20Address, + Data: util.BuildErc20BalanceOfCallData(address), + }, nil) + suite.NoError(err) + + return new(big.Int).SetBytes(resData) +} diff --git a/tests/e2e/testutil/suite.go b/tests/e2e/testutil/suite.go index 5ace3463..a58cff61 100644 --- a/tests/e2e/testutil/suite.go +++ b/tests/e2e/testutil/suite.go @@ -2,8 +2,10 @@ package testutil import ( "fmt" + "math/big" "path/filepath" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/suite" "github.com/kava-labs/kava/app" @@ -30,7 +32,8 @@ type E2eTestSuite struct { Kava *Chain Ibc *Chain - UpgradeHeight int64 + UpgradeHeight int64 + DeployedErc20Address common.Address } func (suite *E2eTestSuite) SetupSuite() { @@ -41,6 +44,7 @@ func (suite *E2eTestSuite) SetupSuite() { suiteConfig := ParseSuiteConfig() suite.config = suiteConfig suite.UpgradeHeight = suiteConfig.KavaUpgradeHeight + suite.DeployedErc20Address = common.HexToAddress(suiteConfig.KavaErc20Address) runnerConfig := runner.Config{ KavaConfigTemplate: suiteConfig.KavaConfigTemplate, @@ -105,3 +109,8 @@ func (suite *E2eTestSuite) SkipIfUpgradeDisabled() { func (suite *E2eTestSuite) KavaHomePath() string { return filepath.Join("kvtool", "full_configs", "generated", "kava", "initstate", ".kava") } + +// BigIntsEqual is a helper method for comparing the equality of two big ints +func (suite *E2eTestSuite) BigIntsEqual(expected *big.Int, actual *big.Int, msg string) { + suite.Truef(expected.Cmp(actual) == 0, "%s (expected: %s, actual: %s)", msg, expected.String(), actual.String()) +} diff --git a/tests/util/erc20.go b/tests/util/erc20.go new file mode 100644 index 00000000..86c8cc6c --- /dev/null +++ b/tests/util/erc20.go @@ -0,0 +1,40 @@ +package util + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "golang.org/x/crypto/sha3" +) + +// EvmContractMethodId encodes a method signature to the method id used in eth calldata. +func EvmContractMethodId(signature string) []byte { + transferFnSignature := []byte(signature) + hash := sha3.NewLegacyKeccak256() + hash.Write(transferFnSignature) + return hash.Sum(nil)[:4] +} + +func BuildErc20TransferCallData(from common.Address, to common.Address, amount *big.Int) []byte { + methodId := EvmContractMethodId("transfer(address,uint256)") + paddedAddress := common.LeftPadBytes(to.Bytes(), 32) + paddedAmount := common.LeftPadBytes(amount.Bytes(), 32) + + var data []byte + data = append(data, methodId...) + data = append(data, paddedAddress...) + data = append(data, paddedAmount...) + + return data +} + +func BuildErc20BalanceOfCallData(address common.Address) []byte { + methodId := EvmContractMethodId("balanceOf(address)") + paddedAddress := common.LeftPadBytes(address.Bytes(), 32) + + var data []byte + data = append(data, methodId...) + data = append(data, paddedAddress...) + + return data +} diff --git a/tests/util/sdksigner.go b/tests/util/sdksigner.go index bb36a375..4c0fe366 100644 --- a/tests/util/sdksigner.go +++ b/tests/util/sdksigner.go @@ -2,10 +2,13 @@ package util import ( "context" + "errors" "fmt" "time" "github.com/kava-labs/kava/app/params" + "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" errorsmod "cosmossdk.io/errors" sdkclient "github.com/cosmos/cosmos-sdk/client" @@ -19,6 +22,10 @@ import ( tmmempool "github.com/tendermint/tendermint/mempool" ) +var ( + ErrSdkBroadcastTimeout = errors.New("timed out waiting for tx to be committed to block") +) + type KavaMsgRequest struct { Msgs []sdk.Msg GasLimit uint64 @@ -420,3 +427,35 @@ func Sign( func GetAccAddress(privKey cryptotypes.PrivKey) sdk.AccAddress { return privKey.PubKey().Address().Bytes() } + +// WaitForSdkTxCommit polls the chain until the tx hash is found or times out. +// Returns an error immediately if tx hash is empty +func WaitForSdkTxCommit(txClient txtypes.ServiceClient, txHash string, timeout time.Duration) (*sdk.TxResponse, error) { + if txHash == "" { + return nil, fmt.Errorf("tx hash is empty") + } + var err error + var txRes *sdk.TxResponse + var res *txtypes.GetTxResponse + outOfTime := time.After(timeout) + for { + select { + case <-outOfTime: + err = ErrSdkBroadcastTimeout + default: + res, err = txClient.GetTx(context.Background(), &txtypes.GetTxRequest{Hash: txHash}) + if err != nil { + status, ok := grpcstatus.FromError(err) + if ok && status.Code() == codes.NotFound { + // tx still not committed to a block. retry! + time.Sleep(100 * time.Millisecond) + continue + } + break + } + txRes = res.TxResponse + } + break + } + return txRes, err +}