mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
test(e2e): support running against live networks (#1630)
* 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
This commit is contained in:
parent
336bac7466
commit
7cff7bec23
1
Makefile
1
Makefile
@ -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:
|
||||
|
@ -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
tests/e2e/.env.live-network-example
Normal file
20
tests/e2e/.env.live-network-example
Normal file
@ -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
|
@ -32,7 +32,7 @@ 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)
|
||||
@ -40,12 +40,12 @@ func setupConvertToCoinTest(
|
||||
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)
|
||||
@ -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)
|
||||
|
||||
///////////////////////////////
|
||||
@ -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)
|
||||
|
||||
///////////////////////////////
|
||||
@ -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()
|
||||
|
@ -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)
|
||||
@ -47,17 +47,17 @@ 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")
|
||||
@ -65,12 +65,12 @@ func (suite *IntegrationTestSuite) TestEthCallToErc20() {
|
||||
|
||||
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
|
||||
@ -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)
|
||||
@ -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(
|
||||
@ -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
|
||||
|
@ -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)
|
||||
@ -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)
|
||||
|
@ -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
|
||||
@ -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",
|
||||
@ -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).
|
||||
@ -119,7 +119,7 @@ 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())
|
||||
@ -127,7 +127,7 @@ func (suite *IntegrationTestSuite) TestIbcTransfer() {
|
||||
gasLimit := int64(2e5)
|
||||
fee := ukava(7500)
|
||||
|
||||
fundsToSend := ukava(5e6) // 5 KAVA
|
||||
fundsToSend := ukava(5e4) // .005 KAVA
|
||||
transferMsg := ibctypes.NewMsgTransfer(
|
||||
testutil.IbcPort,
|
||||
testutil.IbcChannel,
|
||||
|
@ -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 {
|
||||
@ -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
|
||||
|
@ -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")
|
||||
@ -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")
|
||||
|
80
tests/e2e/runner/live.go
Normal file
80
tests/e2e/runner/live.go
Normal file
@ -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.")
|
||||
}
|
@ -15,26 +15,28 @@ type NodeRunner interface {
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// waitForChainStart sets a timeout and repeatedly pings the chains.
|
||||
// If the chain is successfully reached before the timeout, this returns no error.
|
||||
func waitForChainStart(chainDetails ChainDetails) error {
|
||||
// exponential backoff on trying to ping the node, timeout after 30 seconds
|
||||
b := backoff.NewExponentialBackOff()
|
||||
b.MaxInterval = 5 * time.Second
|
||||
b.MaxElapsedTime = 30 * time.Second
|
||||
if err := backoff.Retry(func() error { return pingKava(chainDetails.RpcUrl) }, b); err != nil {
|
||||
return fmt.Errorf("failed to start & connect to chain: %s", err)
|
||||
return fmt.Errorf("failed connect to chain: %s", err)
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
// the evm takes a bit longer to start up. wait for it to start as well.
|
||||
if err := backoff.Retry(func() error { return pingEvm(chainDetails.EvmRpcUrl) }, b); err != nil {
|
||||
return fmt.Errorf("failed to start & connect to chain: %s", err)
|
||||
return fmt.Errorf("failed connect to chain: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pingKava(rpcUrl string) error {
|
||||
log.Println("pinging kava chain...")
|
||||
statusUrl := fmt.Sprintf("%s/status", rpcUrl)
|
||||
log.Printf("pinging kava chain: %s\n", statusUrl)
|
||||
res, err := http.Get(statusUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -27,6 +27,8 @@ import (
|
||||
"github.com/kava-labs/kava/tests/util"
|
||||
)
|
||||
|
||||
// SigningAccount wraps details about an account and its private keys.
|
||||
// It exposes functionality for signing and broadcasting transactions.
|
||||
type SigningAccount struct {
|
||||
name string
|
||||
mnemonic string
|
||||
@ -173,6 +175,8 @@ func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxRespo
|
||||
return response
|
||||
}
|
||||
|
||||
// SignRawEvmData signs raw evm data with the SigningAccount's private key.
|
||||
// It does not broadcast the signed data.
|
||||
func (a *SigningAccount) SignRawEvmData(msg []byte) ([]byte, types.PubKey, error) {
|
||||
keyringSigner := emtests.NewSigner(a.evmPrivKey)
|
||||
return keyringSigner.SignByAddress(a.SdkAddress, msg)
|
||||
|
@ -133,6 +133,7 @@ func (chain *Chain) GetModuleBalances(moduleName string) sdk.Coins {
|
||||
return chain.QuerySdkForBalances(addr)
|
||||
}
|
||||
|
||||
// GetErc20Balance fetches the ERC20 balance of `contract` for `address`.
|
||||
func (chain *Chain) GetErc20Balance(contract, address common.Address) *big.Int {
|
||||
resData, err := chain.EvmClient.CallContract(context.Background(), ethereum.CallMsg{
|
||||
To: &contract,
|
||||
|
@ -13,12 +13,15 @@ func init() {
|
||||
gotenv.Load()
|
||||
}
|
||||
|
||||
// SuiteConfig wraps configuration details for running the end-to-end test suite.
|
||||
type SuiteConfig struct {
|
||||
// A funded account used to fnd all other accounts.
|
||||
FundedAccountMnemonic string
|
||||
|
||||
// A config for using kvtool local networks for the test run
|
||||
Kvtool *KvtoolConfig
|
||||
// A config for connecting to a running network
|
||||
LiveNetwork *LiveNetworkConfig
|
||||
|
||||
// Whether or not to start an IBC chain. Use `suite.SkipIfIbcDisabled()` in IBC tests in IBC tests.
|
||||
IncludeIbcTests bool
|
||||
@ -30,6 +33,8 @@ type SuiteConfig struct {
|
||||
SkipShutdown bool
|
||||
}
|
||||
|
||||
// KvtoolConfig wraps configuration options for running the end-to-end test suite against
|
||||
// a locally running chain. This config must be defined if E2E_RUN_KVTOOL_NETWORKS is true.
|
||||
type KvtoolConfig struct {
|
||||
// The kava.configTemplate flag to be passed to kvtool, usually "master".
|
||||
// This allows one to change the base genesis used to start the chain.
|
||||
@ -45,6 +50,15 @@ type KvtoolConfig struct {
|
||||
KavaUpgradeBaseImageTag string
|
||||
}
|
||||
|
||||
// LiveNetworkConfig wraps configuration options for running the end-to-end test suite
|
||||
// against a live network. It must be defined if E2E_RUN_KVTOOL_NETWORKS is false.
|
||||
type LiveNetworkConfig struct {
|
||||
KavaRpcUrl string
|
||||
KavaGrpcUrl string
|
||||
KavaEvmRpcUrl string
|
||||
}
|
||||
|
||||
// ParseSuiteConfig builds a SuiteConfig from environment variables.
|
||||
func ParseSuiteConfig() SuiteConfig {
|
||||
config := SuiteConfig{
|
||||
// this mnemonic is expected to be a funded account that can seed the funds for all
|
||||
@ -63,11 +77,15 @@ func ParseSuiteConfig() SuiteConfig {
|
||||
if useKvtoolNetworks {
|
||||
kvtoolConfig := ParseKvtoolConfig()
|
||||
config.Kvtool = &kvtoolConfig
|
||||
} else {
|
||||
liveNetworkConfig := ParseLiveNetworkConfig()
|
||||
config.LiveNetwork = &liveNetworkConfig
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// ParseKvtoolConfig builds a KvtoolConfig from environment variables.
|
||||
func ParseKvtoolConfig() KvtoolConfig {
|
||||
config := KvtoolConfig{
|
||||
KavaConfigTemplate: nonemptyStringEnv("E2E_KVTOOL_KAVA_CONFIG_TEMPLATE"),
|
||||
@ -87,6 +105,17 @@ func ParseKvtoolConfig() KvtoolConfig {
|
||||
return config
|
||||
}
|
||||
|
||||
// ParseLiveNetworkConfig builds a LiveNetworkConfig from environment variables.
|
||||
func ParseLiveNetworkConfig() LiveNetworkConfig {
|
||||
return LiveNetworkConfig{
|
||||
KavaRpcUrl: nonemptyStringEnv("E2E_KAVA_RPC_URL"),
|
||||
KavaGrpcUrl: nonemptyStringEnv("E2E_KAVA_GRPC_URL"),
|
||||
KavaEvmRpcUrl: nonemptyStringEnv("E2E_KAVA_EMV_RPC_URL"),
|
||||
}
|
||||
}
|
||||
|
||||
// mustParseBool is a helper method that panics if the env variable `name`
|
||||
// cannot be parsed to a boolean
|
||||
func mustParseBool(name string) bool {
|
||||
envValue := os.Getenv(name)
|
||||
if envValue == "" {
|
||||
@ -99,6 +128,8 @@ func mustParseBool(name string) bool {
|
||||
return value
|
||||
}
|
||||
|
||||
// nonemptyStringEnv is a helper method that panics if the env variable `name`
|
||||
// is empty or undefined.
|
||||
func nonemptyStringEnv(name string) string {
|
||||
value := os.Getenv(name)
|
||||
if value == "" {
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// NewEip712TxBuilder is a helper method for creating an EIP712 signed tx
|
||||
// A tx like this is what a user signing cosmos messages with Metamask would broadcast.
|
||||
func (suite *E2eTestSuite) NewEip712TxBuilder(
|
||||
acc *SigningAccount, chain *Chain, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg, memo string,
|
||||
) client.TxBuilder {
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
|
||||
"github.com/kava-labs/kava/tests/e2e/contracts/greeter"
|
||||
"github.com/kava-labs/kava/tests/util"
|
||||
"github.com/kava-labs/kava/x/earn/types"
|
||||
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types"
|
||||
)
|
||||
|
||||
// InitKavaEvmData is run after the chain is running, but before the tests are run.
|
||||
@ -18,11 +20,37 @@ func (suite *E2eTestSuite) InitKavaEvmData() {
|
||||
whale := suite.Kava.GetAccount(FundedAccountName)
|
||||
|
||||
// ensure funded account has nonzero erc20 balance
|
||||
balance := suite.Kava.GetErc20Balance(suite.DeployedErc20Address, whale.EvmAddress)
|
||||
balance := suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, whale.EvmAddress)
|
||||
if balance.Cmp(big.NewInt(0)) != 1 {
|
||||
panic(fmt.Sprintf("expected funded account (%s) to have erc20 balance", whale.EvmAddress.Hex()))
|
||||
}
|
||||
|
||||
// expect the erc20 to be enabled for conversion to sdk.Coin
|
||||
params, err := suite.Kava.Evmutil.Params(context.Background(), &evmutiltypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to fetch evmutil params during init: %s", err))
|
||||
}
|
||||
found := false
|
||||
erc20Addr := suite.DeployedErc20.Address.Hex()
|
||||
for _, p := range params.Params.EnabledConversionPairs {
|
||||
if common.BytesToAddress(p.KavaERC20Address).Hex() == erc20Addr {
|
||||
found = true
|
||||
suite.DeployedErc20.CosmosDenom = p.Denom
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
panic(fmt.Sprintf("erc20 %s must be enabled for conversion to cosmos coin", erc20Addr))
|
||||
}
|
||||
|
||||
// expect the erc20's cosmos denom to be a supported earn vault
|
||||
_, err = suite.Kava.Earn.Vault(
|
||||
context.Background(),
|
||||
types.NewQueryVaultRequest(suite.DeployedErc20.CosmosDenom),
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to find earn vault with denom %s: %s", suite.DeployedErc20.CosmosDenom, err))
|
||||
}
|
||||
|
||||
// deploy an example contract
|
||||
greeterAddr, _, _, err := greeter.DeployGreeter(
|
||||
whale.evmSigner.Auth,
|
||||
@ -33,6 +61,7 @@ func (suite *E2eTestSuite) InitKavaEvmData() {
|
||||
suite.Kava.ContractAddrs["greeter"] = greeterAddr
|
||||
}
|
||||
|
||||
// FundKavaErc20Balance sends the pre-deployed ERC20 token to the `toAddress`.
|
||||
func (suite *E2eTestSuite) FundKavaErc20Balance(toAddress common.Address, amount *big.Int) EvmTxResponse {
|
||||
// funded account should have erc20 balance
|
||||
whale := suite.Kava.GetAccount(FundedAccountName)
|
||||
@ -42,7 +71,7 @@ func (suite *E2eTestSuite) FundKavaErc20Balance(toAddress common.Address, amount
|
||||
suite.NoError(err)
|
||||
|
||||
req := util.EvmTxRequest{
|
||||
Tx: ethtypes.NewTransaction(nonce, suite.DeployedErc20Address, big.NewInt(0), 1e5, big.NewInt(1e10), data),
|
||||
Tx: ethtypes.NewTransaction(nonce, suite.DeployedErc20.Address, big.NewInt(0), 1e5, big.NewInt(1e10), data),
|
||||
Data: fmt.Sprintf("fund %s with ERC20 balance (%s)", toAddress.Hex(), amount.String()),
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,20 @@ const (
|
||||
IbcChannel = "channel-0"
|
||||
)
|
||||
|
||||
// DeployedErc20 is a type that wraps the details of the pre-deployed erc20 used by the e2e test suite.
|
||||
// The Address comes from SuiteConfig.KavaErc20Address
|
||||
// The CosmosDenom is fetched from the EnabledConversionPairs param of x/evmutil.
|
||||
// The tests expect the following:
|
||||
// - the funded account has a nonzero balance of the erc20
|
||||
// - the erc20 is enabled for conversion to sdk.Coin
|
||||
// - the corresponding sdk.Coin is enabled as an earn vault denom
|
||||
// These requirements are checked in InitKavaEvmData().
|
||||
type DeployedErc20 struct {
|
||||
Address common.Address
|
||||
CosmosDenom string
|
||||
}
|
||||
|
||||
// E2eTestSuite is a testify test suite for running end-to-end integration tests on Kava.
|
||||
type E2eTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
@ -33,9 +47,11 @@ type E2eTestSuite struct {
|
||||
Ibc *Chain
|
||||
|
||||
UpgradeHeight int64
|
||||
DeployedErc20Address common.Address
|
||||
DeployedErc20 DeployedErc20
|
||||
}
|
||||
|
||||
// SetupSuite is run before all tests. It initializes chain connections and sets up the
|
||||
// account used for funding accounts in the tests.
|
||||
func (suite *E2eTestSuite) SetupSuite() {
|
||||
var err error
|
||||
fmt.Println("setting up test suite.")
|
||||
@ -43,25 +59,18 @@ func (suite *E2eTestSuite) SetupSuite() {
|
||||
|
||||
suiteConfig := ParseSuiteConfig()
|
||||
suite.config = suiteConfig
|
||||
suite.DeployedErc20Address = common.HexToAddress(suiteConfig.KavaErc20Address)
|
||||
|
||||
if suiteConfig.Kvtool != nil {
|
||||
suite.UpgradeHeight = suiteConfig.Kvtool.KavaUpgradeHeight
|
||||
|
||||
runnerConfig := runner.KvtoolRunnerConfig{
|
||||
KavaConfigTemplate: suiteConfig.Kvtool.KavaConfigTemplate,
|
||||
|
||||
IncludeIBC: suiteConfig.IncludeIbcTests,
|
||||
ImageTag: "local",
|
||||
|
||||
EnableAutomatedUpgrade: suiteConfig.Kvtool.IncludeAutomatedUpgrade,
|
||||
KavaUpgradeName: suiteConfig.Kvtool.KavaUpgradeName,
|
||||
KavaUpgradeHeight: suiteConfig.Kvtool.KavaUpgradeHeight,
|
||||
KavaUpgradeBaseImageTag: suiteConfig.Kvtool.KavaUpgradeBaseImageTag,
|
||||
|
||||
SkipShutdown: suiteConfig.SkipShutdown,
|
||||
suite.DeployedErc20 = DeployedErc20{
|
||||
Address: common.HexToAddress(suiteConfig.KavaErc20Address),
|
||||
// Denom is fetched in InitKavaEvmData()
|
||||
}
|
||||
suite.runner = runner.NewKvtoolRunner(runnerConfig)
|
||||
|
||||
// setup the correct NodeRunner for the given config
|
||||
if suiteConfig.Kvtool != nil {
|
||||
suite.runner = suite.SetupKvtoolNodeRunner()
|
||||
} else if suiteConfig.LiveNetwork != nil {
|
||||
suite.runner = suite.SetupLiveNetworkNodeRunner()
|
||||
} else {
|
||||
panic("expected either kvtool or live network configs to be defined")
|
||||
}
|
||||
|
||||
chains := suite.runner.StartChains()
|
||||
@ -84,8 +93,13 @@ func (suite *E2eTestSuite) SetupSuite() {
|
||||
suite.InitKavaEvmData()
|
||||
}
|
||||
|
||||
// TearDownSuite is run after all tests have run.
|
||||
// In the event of a panic during the tests, it is run after testify recovers.
|
||||
func (suite *E2eTestSuite) TearDownSuite() {
|
||||
fmt.Println("tearing down test suite.")
|
||||
|
||||
// TODO: track asset denoms & then return all funds to initial funding account.
|
||||
|
||||
// close all account request channels
|
||||
suite.Kava.Shutdown()
|
||||
if suite.Ibc != nil {
|
||||
@ -95,12 +109,55 @@ func (suite *E2eTestSuite) TearDownSuite() {
|
||||
suite.runner.Shutdown()
|
||||
}
|
||||
|
||||
// SetupKvtoolNodeRunner is a helper method for building a KvtoolRunnerConfig from the suite config.
|
||||
func (suite *E2eTestSuite) SetupKvtoolNodeRunner() *runner.KvtoolRunner {
|
||||
// upgrade tests are only supported on kvtool networks
|
||||
suite.UpgradeHeight = suite.config.Kvtool.KavaUpgradeHeight
|
||||
|
||||
runnerConfig := runner.KvtoolRunnerConfig{
|
||||
KavaConfigTemplate: suite.config.Kvtool.KavaConfigTemplate,
|
||||
|
||||
IncludeIBC: suite.config.IncludeIbcTests,
|
||||
ImageTag: "local",
|
||||
|
||||
EnableAutomatedUpgrade: suite.config.Kvtool.IncludeAutomatedUpgrade,
|
||||
KavaUpgradeName: suite.config.Kvtool.KavaUpgradeName,
|
||||
KavaUpgradeHeight: suite.config.Kvtool.KavaUpgradeHeight,
|
||||
KavaUpgradeBaseImageTag: suite.config.Kvtool.KavaUpgradeBaseImageTag,
|
||||
|
||||
SkipShutdown: suite.config.SkipShutdown,
|
||||
}
|
||||
|
||||
return runner.NewKvtoolRunner(runnerConfig)
|
||||
}
|
||||
|
||||
// SetupLiveNetworkNodeRunner is a helper method for building a LiveNodeRunner from the suite config.
|
||||
func (suite *E2eTestSuite) SetupLiveNetworkNodeRunner() *runner.LiveNodeRunner {
|
||||
// live network setup doesn't presently support ibc
|
||||
if suite.config.IncludeIbcTests {
|
||||
panic("ibc tests not supported for live network configuration")
|
||||
}
|
||||
|
||||
runnerConfig := runner.LiveNodeRunnerConfig{
|
||||
KavaRpcUrl: suite.config.LiveNetwork.KavaRpcUrl,
|
||||
KavaGrpcUrl: suite.config.LiveNetwork.KavaGrpcUrl,
|
||||
KavaEvmRpcUrl: suite.config.LiveNetwork.KavaEvmRpcUrl,
|
||||
}
|
||||
|
||||
return runner.NewLiveNodeRunner(runnerConfig)
|
||||
}
|
||||
|
||||
// SkipIfIbcDisabled should be called at the start of tests that require IBC.
|
||||
// It gracefully skips the current test if IBC tests are disabled.
|
||||
func (suite *E2eTestSuite) SkipIfIbcDisabled() {
|
||||
if !suite.config.IncludeIbcTests {
|
||||
suite.T().SkipNow()
|
||||
}
|
||||
}
|
||||
|
||||
// SkipIfUpgradeDisabled should be called at the start of tests that require automated upgrades.
|
||||
// It gracefully skips the current test if upgrades are dissabled.
|
||||
// Note: automated upgrade tests are currently only enabled for Kvtool suite runs.
|
||||
func (suite *E2eTestSuite) SkipIfUpgradeDisabled() {
|
||||
if suite.config.Kvtool != nil && suite.config.Kvtool.IncludeAutomatedUpgrade {
|
||||
suite.T().SkipNow()
|
||||
|
Loading…
Reference in New Issue
Block a user