mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +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.
|
# 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_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.
|
# 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_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 is the tag of the docker image the chain should upgrade from.
|
||||||
E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=
|
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
|
package e2e_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
sdkerrors "cosmossdk.io/errors"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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/e2e/contracts/greeter"
|
||||||
"github.com/kava-labs/kava/tests/util"
|
"github.com/kava-labs/kava/tests/util"
|
||||||
@ -35,3 +44,132 @@ func (suite *IntegrationTestSuite) TestEthCallToGreeterContract() {
|
|||||||
suite.Equal("what's up!", beforeGreeting)
|
suite.Equal("what's up!", beforeGreeting)
|
||||||
suite.Equal(updatedGreeting, afterGreeting)
|
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"
|
sdkmath "cosmossdk.io/math"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
"github.com/cosmos/go-bip39"
|
"github.com/cosmos/go-bip39"
|
||||||
@ -17,6 +18,7 @@ import (
|
|||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/evmos/ethermint/crypto/ethsecp256k1"
|
"github.com/evmos/ethermint/crypto/ethsecp256k1"
|
||||||
|
emtests "github.com/evmos/ethermint/tests"
|
||||||
emtypes "github.com/evmos/ethermint/types"
|
emtypes "github.com/evmos/ethermint/types"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ type SigningAccount struct {
|
|||||||
name string
|
name string
|
||||||
mnemonic string
|
mnemonic string
|
||||||
|
|
||||||
|
evmPrivKey *ethsecp256k1.PrivKey
|
||||||
evmSigner *util.EvmSigner
|
evmSigner *util.EvmSigner
|
||||||
evmReqChan chan<- util.EvmTxRequest
|
evmReqChan chan<- util.EvmTxRequest
|
||||||
evmResChan <-chan util.EvmTxResponse
|
evmResChan <-chan util.EvmTxResponse
|
||||||
@ -66,7 +69,7 @@ func (chain *Chain) AddNewSigningAccount(name string, hdPath *hd.BIP44Params, ch
|
|||||||
|
|
||||||
kavaSigner := util.NewKavaSigner(
|
kavaSigner := util.NewKavaSigner(
|
||||||
chainId,
|
chainId,
|
||||||
chain.encodingConfig,
|
chain.EncodingConfig,
|
||||||
chain.Auth,
|
chain.Auth,
|
||||||
chain.Tx,
|
chain.Tx,
|
||||||
privKey,
|
privKey,
|
||||||
@ -100,6 +103,7 @@ func (chain *Chain) AddNewSigningAccount(name string, hdPath *hd.BIP44Params, ch
|
|||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
l: logger,
|
l: logger,
|
||||||
|
|
||||||
|
evmPrivKey: privKey,
|
||||||
evmSigner: evmSigner,
|
evmSigner: evmSigner,
|
||||||
evmReqChan: evmReqChan,
|
evmReqChan: evmReqChan,
|
||||||
evmResChan: evmResChan,
|
evmResChan: evmResChan,
|
||||||
@ -168,6 +172,11 @@ func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxRespo
|
|||||||
return response
|
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.
|
// NewFundedAccount creates a SigningAccount for a random account & funds the account from the whale.
|
||||||
func (chain *Chain) NewFundedAccount(name string, funds sdk.Coins) *SigningAccount {
|
func (chain *Chain) NewFundedAccount(name string, funds sdk.Coins) *SigningAccount {
|
||||||
entropy, err := bip39.NewEntropy(128)
|
entropy, err := bip39.NewEntropy(128)
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
@ -25,7 +26,6 @@ import (
|
|||||||
|
|
||||||
// Chain wraps query clients & accounts for a network
|
// Chain wraps query clients & accounts for a network
|
||||||
type Chain struct {
|
type Chain struct {
|
||||||
encodingConfig kavaparams.EncodingConfig
|
|
||||||
accounts map[string]*SigningAccount
|
accounts map[string]*SigningAccount
|
||||||
t *testing.T
|
t *testing.T
|
||||||
|
|
||||||
@ -35,10 +35,13 @@ type Chain struct {
|
|||||||
EvmClient *ethclient.Client
|
EvmClient *ethclient.Client
|
||||||
ContractAddrs map[string]common.Address
|
ContractAddrs map[string]common.Address
|
||||||
|
|
||||||
|
EncodingConfig kavaparams.EncodingConfig
|
||||||
|
|
||||||
Auth authtypes.QueryClient
|
Auth authtypes.QueryClient
|
||||||
Bank banktypes.QueryClient
|
Bank banktypes.QueryClient
|
||||||
Community communitytypes.QueryClient
|
Community communitytypes.QueryClient
|
||||||
Earn earntypes.QueryClient
|
Earn earntypes.QueryClient
|
||||||
|
Evm evmtypes.QueryClient
|
||||||
Tm tmservice.ServiceClient
|
Tm tmservice.ServiceClient
|
||||||
Tx txtypes.ServiceClient
|
Tx txtypes.ServiceClient
|
||||||
}
|
}
|
||||||
@ -53,7 +56,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic
|
|||||||
ChainId: details.ChainId,
|
ChainId: details.ChainId,
|
||||||
ContractAddrs: make(map[string]common.Address),
|
ContractAddrs: make(map[string]common.Address),
|
||||||
}
|
}
|
||||||
chain.encodingConfig = app.MakeEncodingConfig()
|
chain.EncodingConfig = app.MakeEncodingConfig()
|
||||||
|
|
||||||
grpcUrl := fmt.Sprintf("http://localhost:%s", details.GrpcPort)
|
grpcUrl := fmt.Sprintf("http://localhost:%s", details.GrpcPort)
|
||||||
grpcConn, err := util.NewGrpcConnection(grpcUrl)
|
grpcConn, err := util.NewGrpcConnection(grpcUrl)
|
||||||
@ -71,6 +74,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic
|
|||||||
chain.Bank = banktypes.NewQueryClient(grpcConn)
|
chain.Bank = banktypes.NewQueryClient(grpcConn)
|
||||||
chain.Community = communitytypes.NewQueryClient(grpcConn)
|
chain.Community = communitytypes.NewQueryClient(grpcConn)
|
||||||
chain.Earn = earntypes.NewQueryClient(grpcConn)
|
chain.Earn = earntypes.NewQueryClient(grpcConn)
|
||||||
|
chain.Evm = evmtypes.NewQueryClient(grpcConn)
|
||||||
chain.Tm = tmservice.NewServiceClient(grpcConn)
|
chain.Tm = tmservice.NewServiceClient(grpcConn)
|
||||||
chain.Tx = txtypes.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.
|
// Tag of kava docker image that will be upgraded to the current image before tests are run, if upgrade is enabled.
|
||||||
KavaUpgradeBaseImageTag string
|
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)
|
// When true, the chains will remain running after tests complete (pass or fail)
|
||||||
SkipShutdown bool
|
SkipShutdown bool
|
||||||
}
|
}
|
||||||
@ -41,6 +44,7 @@ func ParseSuiteConfig() SuiteConfig {
|
|||||||
// new accounts created during tests. it will be available under Accounts["whale"]
|
// new accounts created during tests. it will be available under Accounts["whale"]
|
||||||
FundedAccountMnemonic: nonemptyStringEnv("E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC"),
|
FundedAccountMnemonic: nonemptyStringEnv("E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC"),
|
||||||
KavaConfigTemplate: nonemptyStringEnv("E2E_KVTOOL_KAVA_CONFIG_TEMPLATE"),
|
KavaConfigTemplate: nonemptyStringEnv("E2E_KVTOOL_KAVA_CONFIG_TEMPLATE"),
|
||||||
|
KavaErc20Address: nonemptyStringEnv("E2E_KAVA_ERC20_ADDRESS"),
|
||||||
IncludeIbcTests: mustParseBool("E2E_INCLUDE_IBC_TESTS"),
|
IncludeIbcTests: mustParseBool("E2E_INCLUDE_IBC_TESTS"),
|
||||||
IncludeAutomatedUpgrade: mustParseBool("E2E_INCLUDE_AUTOMATED_UPGRADE"),
|
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
|
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.
|
// 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.
|
// It is used to initialize some EVM state, such as deploying contracts.
|
||||||
func (suite *E2eTestSuite) InitKavaEvmData() {
|
func (suite *E2eTestSuite) InitKavaEvmData() {
|
||||||
whale := suite.Kava.GetAccount(FundedAccountName)
|
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
|
// deploy an example contract
|
||||||
greeterAddr, _, _, err := greeter.DeployGreeter(
|
greeterAddr, _, _, err := greeter.DeployGreeter(
|
||||||
whale.evmSigner.Auth,
|
whale.evmSigner.Auth,
|
||||||
@ -16,3 +33,29 @@ func (suite *E2eTestSuite) InitKavaEvmData() {
|
|||||||
suite.NoError(err, "failed to deploy a contract to the EVM")
|
suite.NoError(err, "failed to deploy a contract to the EVM")
|
||||||
suite.Kava.ContractAddrs["greeter"] = greeterAddr
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
@ -31,6 +33,7 @@ type E2eTestSuite struct {
|
|||||||
Ibc *Chain
|
Ibc *Chain
|
||||||
|
|
||||||
UpgradeHeight int64
|
UpgradeHeight int64
|
||||||
|
DeployedErc20Address common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *E2eTestSuite) SetupSuite() {
|
func (suite *E2eTestSuite) SetupSuite() {
|
||||||
@ -41,6 +44,7 @@ func (suite *E2eTestSuite) SetupSuite() {
|
|||||||
suiteConfig := ParseSuiteConfig()
|
suiteConfig := ParseSuiteConfig()
|
||||||
suite.config = suiteConfig
|
suite.config = suiteConfig
|
||||||
suite.UpgradeHeight = suiteConfig.KavaUpgradeHeight
|
suite.UpgradeHeight = suiteConfig.KavaUpgradeHeight
|
||||||
|
suite.DeployedErc20Address = common.HexToAddress(suiteConfig.KavaErc20Address)
|
||||||
|
|
||||||
runnerConfig := runner.Config{
|
runnerConfig := runner.Config{
|
||||||
KavaConfigTemplate: suiteConfig.KavaConfigTemplate,
|
KavaConfigTemplate: suiteConfig.KavaConfigTemplate,
|
||||||
@ -105,3 +109,8 @@ func (suite *E2eTestSuite) SkipIfUpgradeDisabled() {
|
|||||||
func (suite *E2eTestSuite) KavaHomePath() string {
|
func (suite *E2eTestSuite) KavaHomePath() string {
|
||||||
return filepath.Join("kvtool", "full_configs", "generated", "kava", "initstate", ".kava")
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app/params"
|
"github.com/kava-labs/kava/app/params"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
grpcstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
errorsmod "cosmossdk.io/errors"
|
errorsmod "cosmossdk.io/errors"
|
||||||
sdkclient "github.com/cosmos/cosmos-sdk/client"
|
sdkclient "github.com/cosmos/cosmos-sdk/client"
|
||||||
@ -19,6 +22,10 @@ import (
|
|||||||
tmmempool "github.com/tendermint/tendermint/mempool"
|
tmmempool "github.com/tendermint/tendermint/mempool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSdkBroadcastTimeout = errors.New("timed out waiting for tx to be committed to block")
|
||||||
|
)
|
||||||
|
|
||||||
type KavaMsgRequest struct {
|
type KavaMsgRequest struct {
|
||||||
Msgs []sdk.Msg
|
Msgs []sdk.Msg
|
||||||
GasLimit uint64
|
GasLimit uint64
|
||||||
@ -420,3 +427,35 @@ func Sign(
|
|||||||
func GetAccAddress(privKey cryptotypes.PrivKey) sdk.AccAddress {
|
func GetAccAddress(privKey cryptotypes.PrivKey) sdk.AccAddress {
|
||||||
return privKey.PubKey().Address().Bytes()
|
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