mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 10:17:28 +00:00 
			
		
		
		
	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
This commit is contained in:
		
							parent
							
								
									91e7933a55
								
							
						
					
					
						commit
						3366b3b3e3
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
Subproject commit 6022e137686e6ab648174b3487c3ee82ec7056ae
 | 
			
		||||
Subproject commit 9111087667b4ec136a4dd684762e41515a240a0f
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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"),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										107
									
								
								tests/e2e/testutil/eip712.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								tests/e2e/testutil/eip712.go
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
}
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										40
									
								
								tests/util/erc20.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								tests/util/erc20.go
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
}
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user