mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +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
	 Robert Pirtle
						Robert Pirtle