Skip to content

Commit

Permalink
test(e2e): support running against live networks (#1630)
Browse files Browse the repository at this point in the history
* add NodeRunner impl for connecting to live network

* refactor out node runner setups

* remove hardcoded denom for DeployedErc20

* further specify restrictions on DeployedErc20

* don't override .env funded account mnemonic

* lower amounts for convert to coin e2e tests

* lower fund values used by e2e tests

* add doc comments for all e2e functions & types
  • Loading branch information
pirtleshell committed Jun 26, 2023
1 parent 336bac7 commit 7cff7be
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 54 deletions.
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ test-basic: test

# run end-to-end tests (local docker container must be built, see docker-build)
test-e2e: docker-build
export 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'; \
go test -failfast -count=1 -v ./tests/e2e/...

test:
Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/.env
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ E2E_KAVA_UPGRADE_HEIGHT=
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.
# The E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC account must have a balance.
# The ERC20 must be enabled via x/evmutil params for conversion to sdk.Coin.
# The corresponding sdk.Coin must be a supported vault in x/earn.
E2E_KAVA_ERC20_ADDRESS=0xeA7100edA2f805356291B0E55DaD448599a72C6d
20 changes: 20 additions & 0 deletions tests/e2e/.env.live-network-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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_RUN_KVTOOL_NETWORKS must be false to trigger run on live network
E2E_RUN_KVTOOL_NETWORKS=false

# Configure the endpoints for connecting to the running network here.
E2E_KAVA_RPC_URL='http://localhost:26657'
E2E_KAVA_GRPC_URL='http://localhost:9090'
E2E_KAVA_EMV_RPC_URL='http://localhost:8545'

# E2E_INCLUDE_IBC_TESTS is not currently supported for running tests against a live network.
E2E_INCLUDE_IBC_TESTS=false

# E2E_KAVA_ERC20_ADDRESS is the address of a pre-deployed ERC20 token.
# The E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC account must have a balance.
# The ERC20 must be enabled via x/evmutil params for conversion to sdk.Coin.
# The corresponding sdk.Coin must be a supported vault in x/earn.
E2E_KAVA_ERC20_ADDRESS=0xeA7100edA2f805356291B0E55DaD448599a72C6d
16 changes: 8 additions & 8 deletions tests/e2e/e2e_convert_cosmos_coins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ func setupConvertToCoinTest(
denom = tokenInfo.CosmosDenom
initialFunds = sdk.NewCoins(
sdk.NewInt64Coin(suite.Kava.StakingDenom, 1e6), // gas money
sdk.NewInt64Coin(denom, 1e10), // conversion-enabled cosmos coin
sdk.NewInt64Coin(denom, 1e6), // conversion-enabled cosmos coin
)

user = suite.Kava.NewFundedAccount(accountName, initialFunds)

return denom, initialFunds, user
}

// amount must be less than 1e10
// amount must be less than initial funds (1e6)
func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance(
accountName string, amount int64,
) (user *testutil.SigningAccount, contractAddress *evmutiltypes.InternalEVMAddress, denom string, sdkBalance sdk.Coins) {
if amount > 1e10 {
panic("test erc20 amount must be less than 1e10")
if amount > 1e6 {
panic("test erc20 amount must be less than 1e6")
}

denom, sdkBalance, user = setupConvertToCoinTest(suite, accountName)
Expand Down Expand Up @@ -88,7 +88,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter")

fee := sdk.NewCoins(ukava(7500))
convertAmount := int64(5e9)
convertAmount := int64(5e5)
initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom)

///////////////////////////////
Expand Down Expand Up @@ -168,7 +168,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
func (suite *IntegrationTestSuite) TestEIP712ConvertCosmosCoinsToFromERC20() {
denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter-eip712")

convertAmount := int64(5e9)
convertAmount := int64(5e5)
initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom)

///////////////////////////////
Expand Down Expand Up @@ -325,14 +325,14 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls()
// - check complex conversion flow. bob converts funds they receive on evm back to sdk.Coin
func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
fee := sdk.NewCoins(ukava(7500))
initialAliceAmount := int64(2e6)
initialAliceAmount := int64(2e5)
alice, contractAddress, denom, _ := suite.setupAccountWithCosmosCoinERC20Balance(
"cosmo-coin-converter-complex-alice", initialAliceAmount,
)

gasMoney := sdk.NewCoins(ukava(1e6))
bob := suite.Kava.NewFundedAccount("cosmo-coin-converter-complex-bob", gasMoney)
amount := big.NewInt(1e6)
amount := big.NewInt(1e5)

// bob can't move alice's funds
nonce, err := bob.NextNonce()
Expand Down
26 changes: 13 additions & 13 deletions tests/e2e/e2e_evm_contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (suite *IntegrationTestSuite) TestEthCallToGreeterContract() {
// this test manipulates state of the Greeter contract which means other tests shouldn't use it.

// setup funded account to interact with contract
user := suite.Kava.NewFundedAccount("greeter-contract-user", sdk.NewCoins(ukava(10e6)))
user := suite.Kava.NewFundedAccount("greeter-contract-user", sdk.NewCoins(ukava(1e6)))

greeterAddr := suite.Kava.ContractAddrs["greeter"]
contract, err := greeter.NewGreeter(greeterAddr, suite.Kava.EvmClient)
Expand All @@ -47,30 +47,30 @@ func (suite *IntegrationTestSuite) TestEthCallToGreeterContract() {

func (suite *IntegrationTestSuite) TestEthCallToErc20() {
randoReceiver := util.SdkToEvmAddress(app.RandomAddress())
amount := big.NewInt(1e6)
amount := big.NewInt(1e3)

// make unauthenticated eth_call query to check balance
beforeBalance := suite.Kava.GetErc20Balance(suite.DeployedErc20Address, randoReceiver)
beforeBalance := suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, 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.Kava.GetErc20Balance(suite.DeployedErc20Address, randoReceiver)
afterBalance := suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, 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)))
sender := suite.Kava.NewFundedAccount("eip712-msgSend", sdk.NewCoins(ukava(2e4)))
receiver := app.RandomAddress()

// setup message for sending 1KAVA to random receiver
// setup message for sending some kava to random receiver
msgs := []sdk.Msg{
banktypes.NewMsgSend(sender.SdkAddress, receiver, sdk.NewCoins(ukava(1e6))),
banktypes.NewMsgSend(sender.SdkAddress, receiver, sdk.NewCoins(ukava(1e3))),
}

// create tx
Expand Down Expand Up @@ -103,16 +103,16 @@ func (suite *IntegrationTestSuite) TestEip712BasicMessageAuthorization() {
Denom: "ukava",
})
suite.NoError(err)
suite.Equal(sdk.NewInt(1e6), balRes.Balance.Amount)
suite.Equal(sdk.NewInt(1e3), 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"
amount := sdk.NewInt(1e4) // .04 USDC
sdkDenom := suite.DeployedErc20.CosmosDenom

// create new funded account
depositor := suite.Kava.NewFundedAccount("eip712-earn-depositor", sdk.NewCoins(ukava(1e6)))
depositor := suite.Kava.NewFundedAccount("eip712-earn-depositor", sdk.NewCoins(ukava(1e5)))
// give them erc20 balance to deposit
fundRes := suite.FundKavaErc20Balance(depositor.EvmAddress, amount.BigInt())
suite.NoError(fundRes.Err)
Expand All @@ -121,7 +121,7 @@ func (suite *IntegrationTestSuite) TestEip712ConvertToCoinAndDepositToEarn() {
convertMsg := evmutiltypes.NewMsgConvertERC20ToCoin(
evmutiltypes.NewInternalEVMAddress(depositor.EvmAddress),
depositor.SdkAddress,
evmutiltypes.NewInternalEVMAddress(suite.DeployedErc20Address),
evmutiltypes.NewInternalEVMAddress(suite.DeployedErc20.Address),
amount,
)
depositMsg := earntypes.NewMsgDeposit(
Expand Down Expand Up @@ -161,7 +161,7 @@ func (suite *IntegrationTestSuite) TestEip712ConvertToCoinAndDepositToEarn() {
suite.NoError(err)

// check that depositor no longer has erc20 balance
balance := suite.Kava.GetErc20Balance(suite.DeployedErc20Address, depositor.EvmAddress)
balance := suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, depositor.EvmAddress)
suite.BigIntsEqual(big.NewInt(0), balance, "expected no erc20 balance")

// check that account has an earn deposit position
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/e2e_min_fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (suite *IntegrationTestSuite) TestEthGasPriceReturnsMinFee() {

func (suite *IntegrationTestSuite) TestEvmRespectsMinFee() {
// setup sender & receiver
sender := suite.Kava.NewFundedAccount("evm-min-fee-test-sender", sdk.NewCoins(ukava(2e6)))
sender := suite.Kava.NewFundedAccount("evm-min-fee-test-sender", sdk.NewCoins(ukava(1e3)))
randoReceiver := util.SdkToEvmAddress(app.RandomAddress())

// get min gas price for evm (from app.toml)
Expand All @@ -44,7 +44,7 @@ func (suite *IntegrationTestSuite) TestEvmRespectsMinFee() {
// attempt tx with less than min gas price (min fee - 1)
tooLowGasPrice := minGasPrice.Sub(sdk.OneInt()).BigInt()
req := util.EvmTxRequest{
Tx: ethtypes.NewTransaction(0, randoReceiver, big.NewInt(1e6), 1e5, tooLowGasPrice, nil),
Tx: ethtypes.NewTransaction(0, randoReceiver, big.NewInt(5e2), 1e5, tooLowGasPrice, nil),
Data: "this tx should fail because it's gas price is too low",
}
res := sender.SignAndBroadcastEvmTx(req)
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (suite *IntegrationTestSuite) TestFundedAccount() {
// example test that signs & broadcasts an EVM tx
func (suite *IntegrationTestSuite) TestTransferOverEVM() {
// fund an account that can perform the transfer
initialFunds := ukava(1e7) // 10 KAVA
initialFunds := ukava(1e6) // 1 KAVA
acc := suite.Kava.NewFundedAccount("evm-test-transfer", sdk.NewCoins(initialFunds))

// get a rando account to send kava to
Expand All @@ -93,7 +93,7 @@ func (suite *IntegrationTestSuite) TestTransferOverEVM() {
suite.Equal(uint64(0), nonce) // sanity check. the account should have no prior txs

// transfer kava over EVM
kavaToTransfer := big.NewInt(1e18) // 1 KAVA; akava has 18 decimals.
kavaToTransfer := big.NewInt(1e17) // .1 KAVA; akava has 18 decimals.
req := util.EvmTxRequest{
Tx: ethtypes.NewTransaction(nonce, to, kavaToTransfer, 1e5, minEvmGasPrice, nil),
Data: "any ol' data to track this through the system",
Expand All @@ -109,7 +109,7 @@ func (suite *IntegrationTestSuite) TestTransferOverEVM() {

// expect (9 - gas used) KAVA remaining in account.
balance := suite.Kava.QuerySdkForBalances(acc.SdkAddress)
suite.Equal(sdkmath.NewInt(9e6).Sub(ukavaUsedForGas), balance.AmountOf("ukava"))
suite.Equal(sdkmath.NewInt(9e5).Sub(ukavaUsedForGas), balance.AmountOf("ukava"))
}

// TestIbcTransfer transfers KAVA from the primary kava chain (suite.Kava) to the ibc chain (suite.Ibc).
Expand All @@ -119,15 +119,15 @@ func (suite *IntegrationTestSuite) TestIbcTransfer() {

// ARRANGE
// setup kava account
funds := ukava(1e7) // 10 KAVA
funds := ukava(1e5) // .1 KAVA
kavaAcc := suite.Kava.NewFundedAccount("ibc-transfer-kava-side", sdk.NewCoins(funds))
// setup ibc account
ibcAcc := suite.Ibc.NewFundedAccount("ibc-transfer-ibc-side", sdk.NewCoins())

gasLimit := int64(2e5)
fee := ukava(7500)

fundsToSend := ukava(5e6) // 5 KAVA
fundsToSend := ukava(5e4) // .005 KAVA
transferMsg := ibctypes.NewMsgTransfer(
testutil.IbcPort,
testutil.IbcChannel,
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/runner/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,30 @@ type ChainDetails struct {
StakingDenom string
}

// EvmClient dials the underlying EVM RPC url and returns an ethclient.
func (c ChainDetails) EvmClient() (*ethclient.Client, error) {
return ethclient.Dial(c.EvmRpcUrl)
}

// GrpcConn creates a new connection to the underlying Grpc url.
func (c ChainDetails) GrpcConn() (*grpc.ClientConn, error) {
return util.NewGrpcConnection(c.GrpcUrl)
}

// Chains wraps a map of name -> details about how to connect to a chain.
// It prevents registering multiple chains with the same name & encapsulates
// panicking if attempting to access a chain that does not exist.
type Chains struct {
byName map[string]*ChainDetails
}

// NewChains creates an empty Chains map.
func NewChains() Chains {
return Chains{byName: make(map[string]*ChainDetails, 0)}
}

// MustGetChain returns the chain of a given name,
// or panics if a chain with that name has not been registered.
func (c Chains) MustGetChain(name string) *ChainDetails {
chain, found := c.byName[name]
if !found {
Expand All @@ -48,6 +56,8 @@ func (c Chains) MustGetChain(name string) *ChainDetails {
return chain
}

// Register adds a chain to the map.
// It returns an error if a ChainDetails with that name has already been registered.
func (c *Chains) Register(name string, chain *ChainDetails) error {
if _, found := c.byName[name]; found {
return ErrChainAlreadyExists
Expand Down
7 changes: 7 additions & 0 deletions tests/e2e/runner/kvtool.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ type KvtoolRunner struct {

var _ NodeRunner = &KvtoolRunner{}

// NewKvtoolRunner creates a new KvtoolRunner.
func NewKvtoolRunner(config KvtoolRunnerConfig) *KvtoolRunner {
return &KvtoolRunner{
config: config,
}
}

// StartChains implements NodeRunner.
// For KvtoolRunner, it sets up, runs, and connects to a local chain via kvtool.
func (k *KvtoolRunner) StartChains() Chains {
// install kvtool if not already installed
installKvtoolCmd := exec.Command("./scripts/install-kvtool.sh")
Expand Down Expand Up @@ -89,6 +92,10 @@ func (k *KvtoolRunner) StartChains() Chains {
return chains
}

// Shutdown implements NodeRunner.
// For KvtoolRunner, it shuts down the local kvtool network.
// To prevent shutting down the chain (eg. to preserve logs or examine post-test state)
// use the `SkipShutdown` option on the config.
func (k *KvtoolRunner) Shutdown() {
if k.config.SkipShutdown {
log.Printf("would shut down but SkipShutdown is true")
Expand Down
80 changes: 80 additions & 0 deletions tests/e2e/runner/live.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package runner

import (
"context"
"fmt"

"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// LiveNodeRunnerConfig implements NodeRunner.
// It connects to a running network via the RPC, GRPC, and EVM urls.
type LiveNodeRunnerConfig struct {
KavaRpcUrl string
KavaGrpcUrl string
KavaEvmRpcUrl string
}

// LiveNodeRunner implements NodeRunner for an already-running chain.
// If a LiveNodeRunner is used, end-to-end tests are run against a live chain.
type LiveNodeRunner struct {
config LiveNodeRunnerConfig
}

var _ NodeRunner = LiveNodeRunner{}

// NewLiveNodeRunner creates a new LiveNodeRunner.
func NewLiveNodeRunner(config LiveNodeRunnerConfig) *LiveNodeRunner {
return &LiveNodeRunner{config}
}

// StartChains implements NodeRunner.
// It initializes connections to the chain based on parameters.
// It attempts to ping the necessary endpoints and panics if they cannot be reached.
func (r LiveNodeRunner) StartChains() Chains {
fmt.Println("establishing connection to live kava network")
chains := NewChains()

kavaChain := ChainDetails{
RpcUrl: r.config.KavaRpcUrl,
GrpcUrl: r.config.KavaGrpcUrl,
EvmRpcUrl: r.config.KavaEvmRpcUrl,
}

if err := waitForChainStart(kavaChain); err != nil {
panic(fmt.Sprintf("failed to ping chain: %s", err))
}

// determine chain id
grpc, err := kavaChain.GrpcConn()
if err != nil {
panic(fmt.Sprintf("failed to establish grpc conn to %s: %s", r.config.KavaGrpcUrl, err))
}
tm := tmservice.NewServiceClient(grpc)
nodeInfo, err := tm.GetNodeInfo(context.Background(), &tmservice.GetNodeInfoRequest{})
if err != nil {
panic(fmt.Sprintf("failed to fetch kava node info: %s", err))
}
kavaChain.ChainId = nodeInfo.DefaultNodeInfo.Network

// determine staking denom
staking := stakingtypes.NewQueryClient(grpc)
stakingParams, err := staking.Params(context.Background(), &stakingtypes.QueryParamsRequest{})
if err != nil {
panic(fmt.Sprintf("failed to fetch kava staking params: %s", err))
}
kavaChain.StakingDenom = stakingParams.Params.BondDenom

chains.Register("kava", &kavaChain)

fmt.Printf("successfully connected to live network %+v\n", kavaChain)

return chains
}

// Shutdown implements NodeRunner.
// As the chains are externally operated, this is a no-op.
func (LiveNodeRunner) Shutdown() {
fmt.Println("shutting down e2e test connections.")
}
Loading

0 comments on commit 7cff7be

Please sign in to comment.