test(e2e): lower cost to run on live networks (#1646)

* add cost summary to e2e test suite runs

* lower cost of test

* refactor initial funding to use new BankSend()

* reduce gas used for initial funding

* return all sdk funds on shutdown

* enable refunds when running against live network

* save more cosmos coins! require less total overall

* track erc20s on Chain

* refactor erc20 funding with new TransferErc20()

* return all registered erc20 balance

* lower erc20 e2e test values

* withdraw earn position & convert back to erc20

* lower gas fees in e2e tests

* fix env variable typo

* add readme note about how to run on live network

* log total spend required for e2e tests
This commit is contained in:
Robert Pirtle 2023-06-29 22:30:02 -07:00 committed by GitHub
parent 7cff7bec23
commit 5cfa33f638
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 251 additions and 54 deletions

View File

@ -8,7 +8,7 @@ E2E_RUN_KVTOOL_NETWORKS=false
# Configure the endpoints for connecting to the running network here. # Configure the endpoints for connecting to the running network here.
E2E_KAVA_RPC_URL='http://localhost:26657' E2E_KAVA_RPC_URL='http://localhost:26657'
E2E_KAVA_GRPC_URL='http://localhost:9090' E2E_KAVA_GRPC_URL='http://localhost:9090'
E2E_KAVA_EMV_RPC_URL='http://localhost:8545' E2E_KAVA_EVM_RPC_URL='http://localhost:8545'
# E2E_INCLUDE_IBC_TESTS is not currently supported for running tests against a live network. # E2E_INCLUDE_IBC_TESTS is not currently supported for running tests against a live network.
E2E_INCLUDE_IBC_TESTS=false E2E_INCLUDE_IBC_TESTS=false

View File

@ -2,6 +2,7 @@ package e2e_test
import ( import (
"context" "context"
"fmt"
"math/big" "math/big"
"time" "time"
@ -16,6 +17,8 @@ import (
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" evmutiltypes "github.com/kava-labs/kava/x/evmutil/types"
) )
const initialCosmosCoinConversionDenomFunds = int64(1e4)
func setupConvertToCoinTest( func setupConvertToCoinTest(
suite *IntegrationTestSuite, accountName string, suite *IntegrationTestSuite, accountName string,
) (denom string, initialFunds sdk.Coins, user *testutil.SigningAccount) { ) (denom string, initialFunds sdk.Coins, user *testutil.SigningAccount) {
@ -31,8 +34,8 @@ func setupConvertToCoinTest(
tokenInfo := params.Params.AllowedCosmosDenoms[0] tokenInfo := params.Params.AllowedCosmosDenoms[0]
denom = tokenInfo.CosmosDenom denom = tokenInfo.CosmosDenom
initialFunds = sdk.NewCoins( initialFunds = sdk.NewCoins(
sdk.NewInt64Coin(suite.Kava.StakingDenom, 1e6), // gas money sdk.NewInt64Coin(suite.Kava.StakingDenom, 1e5), // gas money
sdk.NewInt64Coin(denom, 1e6), // conversion-enabled cosmos coin sdk.NewInt64Coin(denom, initialCosmosCoinConversionDenomFunds), // conversion-enabled cosmos coin
) )
user = suite.Kava.NewFundedAccount(accountName, initialFunds) user = suite.Kava.NewFundedAccount(accountName, initialFunds)
@ -40,19 +43,17 @@ func setupConvertToCoinTest(
return denom, initialFunds, user return denom, initialFunds, user
} }
// amount must be less than initial funds (1e6) // amount must be less than initial funds (initialCosmosCoinConversionDenomFunds)
func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance( func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance(
accountName string, amount int64, accountName string, amount int64,
) (user *testutil.SigningAccount, contractAddress *evmutiltypes.InternalEVMAddress, denom string, sdkBalance sdk.Coins) { ) (user *testutil.SigningAccount, contractAddress *evmutiltypes.InternalEVMAddress, denom string, sdkBalance sdk.Coins) {
if amount > 1e6 { if amount > initialCosmosCoinConversionDenomFunds {
panic("test erc20 amount must be less than 1e6") panic(fmt.Sprintf("test erc20 amount must be less than %d", initialCosmosCoinConversionDenomFunds))
} }
denom, sdkBalance, user = setupConvertToCoinTest(suite, accountName) denom, sdkBalance, user = setupConvertToCoinTest(suite, accountName)
convertAmount := sdk.NewInt64Coin(denom, amount) convertAmount := sdk.NewInt64Coin(denom, amount)
fee := sdk.NewCoins(ukava(7500))
// setup user to have erc20 balance // setup user to have erc20 balance
msg := evmutiltypes.NewMsgConvertCosmosCoinToERC20( msg := evmutiltypes.NewMsgConvertCosmosCoinToERC20(
user.SdkAddress.String(), user.SdkAddress.String(),
@ -61,8 +62,8 @@ func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance(
) )
tx := util.KavaMsgRequest{ tx := util.KavaMsgRequest{
Msgs: []sdk.Msg{&msg}, Msgs: []sdk.Msg{&msg},
GasLimit: 2e6, GasLimit: 4e5,
FeeAmount: fee, FeeAmount: sdk.NewCoins(ukava(400)),
Data: "converting sdk coin to erc20", Data: "converting sdk coin to erc20",
} }
res := user.SignAndBroadcastKavaTx(tx) res := user.SignAndBroadcastKavaTx(tx)
@ -87,8 +88,7 @@ func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance(
func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() { func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter") denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter")
fee := sdk.NewCoins(ukava(7500)) convertAmount := int64(5e3)
convertAmount := int64(5e5)
initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom)
/////////////////////////////// ///////////////////////////////
@ -102,7 +102,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
tx := util.KavaMsgRequest{ tx := util.KavaMsgRequest{
Msgs: []sdk.Msg{&convertToErc20Msg}, Msgs: []sdk.Msg{&convertToErc20Msg},
GasLimit: 2e6, GasLimit: 2e6,
FeeAmount: fee, FeeAmount: sdk.NewCoins(ukava(2000)),
Data: "converting sdk coin to erc20", Data: "converting sdk coin to erc20",
} }
res := user.SignAndBroadcastKavaTx(tx) res := user.SignAndBroadcastKavaTx(tx)
@ -144,7 +144,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
tx = util.KavaMsgRequest{ tx = util.KavaMsgRequest{
Msgs: []sdk.Msg{&convertFromErc20Msg}, Msgs: []sdk.Msg{&convertFromErc20Msg},
GasLimit: 2e5, GasLimit: 2e5,
FeeAmount: fee, FeeAmount: sdk.NewCoins(ukava(200)),
Data: "converting erc20 to cosmos coin", Data: "converting erc20 to cosmos coin",
} }
res = user.SignAndBroadcastKavaTx(tx) res = user.SignAndBroadcastKavaTx(tx)
@ -168,7 +168,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
func (suite *IntegrationTestSuite) TestEIP712ConvertCosmosCoinsToFromERC20() { func (suite *IntegrationTestSuite) TestEIP712ConvertCosmosCoinsToFromERC20() {
denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter-eip712") denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter-eip712")
convertAmount := int64(5e5) convertAmount := int64(5e3)
initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom)
/////////////////////////////// ///////////////////////////////
@ -237,7 +237,7 @@ func (suite *IntegrationTestSuite) TestEIP712ConvertCosmosCoinsToFromERC20() {
user, user,
suite.Kava, suite.Kava,
2e5, 2e5,
sdk.NewCoins(ukava(1e4)), sdk.NewCoins(ukava(200)),
[]sdk.Msg{&convertFromErc20Msg}, []sdk.Msg{&convertFromErc20Msg},
"", "",
).GetTx() ).GetTx()
@ -270,7 +270,9 @@ func (suite *IntegrationTestSuite) TestEIP712ConvertCosmosCoinsToFromERC20() {
} }
func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls() { func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls() {
user, contractAddress, _, _ := suite.setupAccountWithCosmosCoinERC20Balance("cosmo-coin-converter-unhappy", 1e6) // give them erc20 balance so we know that's not preventing these method calls
// this test sacrifices the 1 coin by never returning it to the sdk.
user, contractAddress, _, _ := suite.setupAccountWithCosmosCoinERC20Balance("cosmo-coin-converter-unhappy", 1)
suite.Run("users can't mint()", func() { suite.Run("users can't mint()", func() {
data := util.BuildErc20MintCallData(user.EvmAddress, big.NewInt(1)) data := util.BuildErc20MintCallData(user.EvmAddress, big.NewInt(1))
@ -282,7 +284,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls()
&ethtypes.LegacyTx{ &ethtypes.LegacyTx{
Nonce: nonce, Nonce: nonce,
GasPrice: minEvmGasPrice, GasPrice: minEvmGasPrice,
Gas: 1e6, Gas: 1e5,
To: &contractAddress.Address, To: &contractAddress.Address,
Data: data, Data: data,
}, },
@ -306,7 +308,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls()
&ethtypes.LegacyTx{ &ethtypes.LegacyTx{
Nonce: nonce, Nonce: nonce,
GasPrice: minEvmGasPrice, GasPrice: minEvmGasPrice,
Gas: 1e6, Gas: 1e5,
To: &contractAddress.Address, To: &contractAddress.Address,
Data: data, Data: data,
}, },
@ -324,15 +326,14 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls()
// - check approval flow of erc20. alice approves bob to move their funds // - check approval flow of erc20. alice approves bob to move their funds
// - check complex conversion flow. bob converts funds they receive on evm back to sdk.Coin // - check complex conversion flow. bob converts funds they receive on evm back to sdk.Coin
func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() { func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
fee := sdk.NewCoins(ukava(7500)) initialAliceAmount := int64(2e3)
initialAliceAmount := int64(2e5)
alice, contractAddress, denom, _ := suite.setupAccountWithCosmosCoinERC20Balance( alice, contractAddress, denom, _ := suite.setupAccountWithCosmosCoinERC20Balance(
"cosmo-coin-converter-complex-alice", initialAliceAmount, "cosmo-coin-converter-complex-alice", initialAliceAmount,
) )
gasMoney := sdk.NewCoins(ukava(1e6)) gasMoney := sdk.NewCoins(ukava(1e5))
bob := suite.Kava.NewFundedAccount("cosmo-coin-converter-complex-bob", gasMoney) bob := suite.Kava.NewFundedAccount("cosmo-coin-converter-complex-bob", gasMoney)
amount := big.NewInt(1e5) amount := big.NewInt(1e3) // test assumes this is half of alice's balance.
// bob can't move alice's funds // bob can't move alice's funds
nonce, err := bob.NextNonce() nonce, err := bob.NextNonce()
@ -340,7 +341,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
transferFromTxData := &ethtypes.LegacyTx{ transferFromTxData := &ethtypes.LegacyTx{
Nonce: nonce, Nonce: nonce,
GasPrice: minEvmGasPrice, GasPrice: minEvmGasPrice,
Gas: 1e6, Gas: 1e5,
To: &contractAddress.Address, To: &contractAddress.Address,
Value: &big.Int{}, Value: &big.Int{},
Data: util.BuildErc20TransferFromCallData(alice.EvmAddress, bob.EvmAddress, amount), Data: util.BuildErc20TransferFromCallData(alice.EvmAddress, bob.EvmAddress, amount),
@ -360,7 +361,7 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
Tx: ethtypes.NewTx(&ethtypes.LegacyTx{ Tx: ethtypes.NewTx(&ethtypes.LegacyTx{
Nonce: nonce, Nonce: nonce,
GasPrice: minEvmGasPrice, GasPrice: minEvmGasPrice,
Gas: 1e6, Gas: 1e5,
To: &contractAddress.Address, To: &contractAddress.Address,
Value: &big.Int{}, Value: &big.Int{},
Data: util.BuildErc20ApproveCallData(bob.EvmAddress, amount), Data: util.BuildErc20ApproveCallData(bob.EvmAddress, amount),
@ -410,8 +411,8 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
) )
convertTx := util.KavaMsgRequest{ convertTx := util.KavaMsgRequest{
Msgs: []sdk.Msg{&convertMsg}, Msgs: []sdk.Msg{&convertMsg},
GasLimit: 2e6, GasLimit: 2e5,
FeeAmount: fee, FeeAmount: sdk.NewCoins(ukava(200)),
Data: "bob converts his new erc20 to an sdk.Coin", Data: "bob converts his new erc20 to an sdk.Coin",
} }
convertRes := bob.SignAndBroadcastKavaTx(convertTx) convertRes := bob.SignAndBroadcastKavaTx(convertTx)
@ -423,4 +424,17 @@ func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
// bob should have sdk balance // bob should have sdk balance
balance := suite.Kava.QuerySdkForBalances(bob.SdkAddress).AmountOf(denom) balance := suite.Kava.QuerySdkForBalances(bob.SdkAddress).AmountOf(denom)
suite.Equal(sdk.NewIntFromBigInt(amount), balance) suite.Equal(sdk.NewIntFromBigInt(amount), balance)
// alice should have the remaining balance
erc20Balance = suite.Kava.GetErc20Balance(contractAddress.Address, alice.EvmAddress)
suite.BigIntsEqual(amount, erc20Balance, "expected alice to have half initial funds remaining")
// convert alice's remaining balance back to sdk coins
convertMsg = evmutiltypes.NewMsgConvertCosmosCoinFromERC20(
alice.EvmAddress.Hex(),
alice.SdkAddress.String(),
sdk.NewInt64Coin(denom, amount.Int64()),
)
convertRes = alice.SignAndBroadcastKavaTx(convertTx)
suite.NoError(convertRes.Err)
} }

View File

@ -47,7 +47,7 @@ func (suite *IntegrationTestSuite) TestEthCallToGreeterContract() {
func (suite *IntegrationTestSuite) TestEthCallToErc20() { func (suite *IntegrationTestSuite) TestEthCallToErc20() {
randoReceiver := util.SdkToEvmAddress(app.RandomAddress()) randoReceiver := util.SdkToEvmAddress(app.RandomAddress())
amount := big.NewInt(1e3) amount := big.NewInt(1)
// make unauthenticated eth_call query to check balance // make unauthenticated eth_call query to check balance
beforeBalance := suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, randoReceiver) beforeBalance := suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, randoReceiver)
@ -108,7 +108,7 @@ func (suite *IntegrationTestSuite) TestEip712BasicMessageAuthorization() {
// Note that this test works because the deployed erc20 is configured in evmutil & earn params. // Note that this test works because the deployed erc20 is configured in evmutil & earn params.
func (suite *IntegrationTestSuite) TestEip712ConvertToCoinAndDepositToEarn() { func (suite *IntegrationTestSuite) TestEip712ConvertToCoinAndDepositToEarn() {
amount := sdk.NewInt(1e4) // .04 USDC amount := sdk.NewInt(1e2) // 0.0002 USDC
sdkDenom := suite.DeployedErc20.CosmosDenom sdkDenom := suite.DeployedErc20.CosmosDenom
// create new funded account // create new funded account
@ -172,4 +172,24 @@ func (suite *IntegrationTestSuite) TestEip712ConvertToCoinAndDepositToEarn() {
suite.NoError(err) suite.NoError(err)
suite.Len(earnRes.Deposits, 1) suite.Len(earnRes.Deposits, 1)
suite.Equal(sdk.NewDecFromInt(amount), earnRes.Deposits[0].Shares.AmountOf(sdkDenom)) suite.Equal(sdk.NewDecFromInt(amount), earnRes.Deposits[0].Shares.AmountOf(sdkDenom))
// withdraw deposit & convert back to erc20 (this allows refund to recover erc20s used in test)
withdraw := earntypes.NewMsgWithdraw(
depositor.SdkAddress.String(),
sdk.NewCoin(sdkDenom, amount),
earntypes.STRATEGY_TYPE_SAVINGS,
)
convertBack := evmutiltypes.NewMsgConvertCoinToERC20(
depositor.SdkAddress.String(),
depositor.EvmAddress.Hex(),
sdk.NewCoin(sdkDenom, amount),
)
withdrawAndConvertBack := util.KavaMsgRequest{
Msgs: []sdk.Msg{withdraw, &convertBack},
GasLimit: 3e5,
FeeAmount: sdk.NewCoins(ukava(300)),
Data: "withdrawing from earn & converting back to erc20",
}
lastRes := depositor.SignAndBroadcastKavaTx(withdrawAndConvertBack)
suite.NoError(lastRes.Err)
} }

View File

@ -57,7 +57,7 @@ func (suite *IntegrationTestSuite) TestChainID() {
// example test that funds a new account & queries its balance // example test that funds a new account & queries its balance
func (suite *IntegrationTestSuite) TestFundedAccount() { func (suite *IntegrationTestSuite) TestFundedAccount() {
funds := ukava(1e7) funds := ukava(1e3)
acc := suite.Kava.NewFundedAccount("example-acc", sdk.NewCoins(funds)) acc := suite.Kava.NewFundedAccount("example-acc", sdk.NewCoins(funds))
// check that the sdk & evm signers are for the same account // check that the sdk & evm signers are for the same account
@ -125,7 +125,7 @@ func (suite *IntegrationTestSuite) TestIbcTransfer() {
ibcAcc := suite.Ibc.NewFundedAccount("ibc-transfer-ibc-side", sdk.NewCoins()) ibcAcc := suite.Ibc.NewFundedAccount("ibc-transfer-ibc-side", sdk.NewCoins())
gasLimit := int64(2e5) gasLimit := int64(2e5)
fee := ukava(7500) fee := ukava(200)
fundsToSend := ukava(5e4) // .005 KAVA fundsToSend := ukava(5e4) // .005 KAVA
transferMsg := ibctypes.NewMsgTransfer( transferMsg := ibctypes.NewMsgTransfer(

View File

@ -21,6 +21,19 @@ The variables in `.env` will not override variables that are already present in
ie. Running `E2E_INCLUDE_IBC_TESTS=false make test-e2e` will disable the ibc tests regardless of how ie. Running `E2E_INCLUDE_IBC_TESTS=false make test-e2e` will disable the ibc tests regardless of how
the variable is set in `.env`. the variable is set in `.env`.
### Running on Live Network
The end-to-end tests support being run on a live network. The primary toggle for setting up the suite to use a live network is the `E2E_RUN_KVTOOL_NETWORKS` flag. When set exactly to `false`, the configuration requires the following three environment variables:
* `E2E_KAVA_RPC_URL`
* `E2E_KAVA_GRPC_URL`
* `E2E_KAVA_EVM_RPC_URL`
See an example environment configuration with full description of all supported configurations in [`.env.live-network-example`](./.env.live-network-example). This example expects a local kvtool network to be running: `kvtool testnet bootstrap`.
When run against a live network, the suite will automatically return all the sdk funds sent to `SigningAccount`s on the chain, and will return any ERC20 balance from those accounts if the ERC20 is registered via `Chain.RegisterERC20`. The pre-deployed ERC20 that is required for the tests is registered on setup.
At this time, live-network tests do not support `E2E_INCLUDE_IBC_TESTS=true` and they do not support automated upgrades.
## `Chain`s ## `Chain`s
A `testutil.Chain` is the abstraction around details, query clients, & signing accounts for interacting with a A `testutil.Chain` is the abstraction around details, query clients, & signing accounts for interacting with a

View File

@ -5,6 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log" "log"
"math/big"
"os" "os"
"time" "time"
@ -47,6 +48,8 @@ type SigningAccount struct {
EvmAddress common.Address EvmAddress common.Address
SdkAddress sdk.AccAddress SdkAddress sdk.AccAddress
gasDenom string
l *log.Logger l *log.Logger
} }
@ -106,6 +109,8 @@ func (chain *Chain) AddNewSigningAccount(name string, hdPath *hd.BIP44Params, ch
mnemonic: mnemonic, mnemonic: mnemonic,
l: logger, l: logger,
gasDenom: chain.StakingDenom,
evmPrivKey: privKey, evmPrivKey: privKey,
evmSigner: evmSigner, evmSigner: evmSigner,
evmReqChan: evmReqChan, evmReqChan: evmReqChan,
@ -170,6 +175,7 @@ func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxRespo
} }
// if we don't have a tx receipt within a given timeout, fail the request // if we don't have a tx receipt within a given timeout, fail the request
a.l.Printf("awaiting evm tx receipt for tx %s\n", res.TxHash)
response.Receipt, response.Err = util.WaitForEvmTxReceipt(a.evmSigner.EvmClient, res.TxHash, 10*time.Second) response.Receipt, response.Err = util.WaitForEvmTxReceipt(a.evmSigner.EvmClient, res.TxHash, 10*time.Second)
return response return response
@ -201,18 +207,11 @@ func (chain *Chain) NewFundedAccount(name string, funds sdk.Coins) *SigningAccou
return acc return acc
} }
// TODO: verify whale has funds.
whale := chain.GetAccount(FundedAccountName) whale := chain.GetAccount(FundedAccountName)
whale.l.Printf("attempting to fund created account (%s=%s)\n", name, acc.SdkAddress.String()) whale.l.Printf("attempting to fund created account (%s=%s)\n", name, acc.SdkAddress.String())
res := whale.SignAndBroadcastKavaTx( res := whale.BankSend(acc.SdkAddress, funds)
util.KavaMsgRequest{
Msgs: []sdk.Msg{
banktypes.NewMsgSend(whale.SdkAddress, acc.SdkAddress, funds),
},
GasLimit: 2e5,
FeeAmount: sdk.NewCoins(sdk.NewCoin(chain.StakingDenom, sdkmath.NewInt(75000))),
Data: fmt.Sprintf("initial funding of account %s", name),
},
)
require.NoErrorf(chain.t, res.Err, "failed to fund new account %s: %s", name, res.Err) require.NoErrorf(chain.t, res.Err, "failed to fund new account %s: %s", name, res.Err)
@ -225,3 +224,32 @@ func (chain *Chain) NewFundedAccount(name string, funds sdk.Coins) *SigningAccou
func (a *SigningAccount) NextNonce() (uint64, error) { func (a *SigningAccount) NextNonce() (uint64, error) {
return a.evmSigner.EvmClient.PendingNonceAt(context.Background(), a.EvmAddress) return a.evmSigner.EvmClient.PendingNonceAt(context.Background(), a.EvmAddress)
} }
// BankSend is a helper method for sending funds via x/bank's MsgSend
func (a *SigningAccount) BankSend(to sdk.AccAddress, amount sdk.Coins) util.KavaMsgResponse {
return a.SignAndBroadcastKavaTx(
util.KavaMsgRequest{
Msgs: []sdk.Msg{banktypes.NewMsgSend(a.SdkAddress, to, amount)},
GasLimit: 2e5, // 200,000 gas
FeeAmount: sdk.NewCoins(sdk.NewCoin(a.gasDenom, sdkmath.NewInt(200))), // assume min gas price of .001ukava
Data: fmt.Sprintf("sending %s to %s", amount, to),
},
)
}
// TransferErc20 is a helper method for sending an erc20 token
func (a *SigningAccount) TransferErc20(contract, to common.Address, amount *big.Int) (EvmTxResponse, error) {
data := util.BuildErc20TransferCallData(to, amount)
nonce, err := a.NextNonce()
if err != nil {
return EvmTxResponse{}, err
}
req := util.EvmTxRequest{
Tx: ethtypes.NewTransaction(nonce, contract, big.NewInt(0), 1e5, big.NewInt(1e10), data),
Data: fmt.Sprintf("fund %s with ERC20 balance (%s)", to.Hex(), amount.String()),
}
res := a.SignAndBroadcastEvmTx(req)
return res, res.Err
}

View File

@ -42,6 +42,7 @@ type Chain struct {
EvmClient *ethclient.Client EvmClient *ethclient.Client
ContractAddrs map[string]common.Address ContractAddrs map[string]common.Address
erc20s map[common.Address]struct{}
EncodingConfig kavaparams.EncodingConfig EncodingConfig kavaparams.EncodingConfig
@ -66,6 +67,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic
StakingDenom: details.StakingDenom, StakingDenom: details.StakingDenom,
ChainId: details.ChainId, ChainId: details.ChainId,
ContractAddrs: make(map[string]common.Address), ContractAddrs: make(map[string]common.Address),
erc20s: make(map[common.Address]struct{}),
} }
chain.EncodingConfig = app.MakeEncodingConfig() chain.EncodingConfig = app.MakeEncodingConfig()
@ -118,6 +120,57 @@ func (chain *Chain) Shutdown() {
} }
} }
// ReturnAllFunds loops through all SigningAccounts and sends all their funds back to the
// initially funded account.
func (chain *Chain) ReturnAllFunds() {
whale := chain.GetAccount(FundedAccountName)
fmt.Println(chain.erc20s)
for _, a := range chain.accounts {
if a.SdkAddress.String() != whale.SdkAddress.String() {
// NOTE: assumes all cosmos coin conversion funds have been converted back to sdk.
// return all erc20 balance
for erc20Addr := range chain.erc20s {
erc20Bal := chain.GetErc20Balance(erc20Addr, a.EvmAddress)
// if account has no balance, do nothing
if erc20Bal.Cmp(big.NewInt(0)) == 0 {
continue
}
_, err := a.TransferErc20(erc20Addr, whale.EvmAddress, erc20Bal)
if err != nil {
a.l.Printf("FAILED TO RETURN ERC20 FUNDS (contract: %s, balance: %d): %s\n",
erc20Addr, erc20Bal, err,
)
}
}
// get sdk balance of account
balance := chain.QuerySdkForBalances(a.SdkAddress)
// assumes 200,000 gas w/ min fee of .001
gas := sdk.NewInt64Coin(chain.StakingDenom, 200)
// ensure they have enough gas to return funds
if balance.AmountOf(chain.StakingDenom).LT(gas.Amount) {
a.l.Printf("ACCOUNT LACKS GAS MONEY TO RETURN FUNDS: %s\n", balance)
continue
}
// send it all back (minus gas) to the whale!
res := a.BankSend(whale.SdkAddress, balance.Sub(gas))
if res.Err != nil {
a.l.Printf("failed to return funds: %s\n", res.Err)
}
}
}
}
// RegisterErc20 is a method to record the address of erc20s on this chain.
// The full balances of each registered erc20 will be returned to the funded
// account when ReturnAllFunds is called.
func (chain *Chain) RegisterErc20(address common.Address) {
chain.erc20s[address] = struct{}{}
}
// QuerySdkForBalances gets the balance of a particular address on this Chain. // QuerySdkForBalances gets the balance of a particular address on this Chain.
func (chain *Chain) QuerySdkForBalances(addr sdk.AccAddress) sdk.Coins { func (chain *Chain) QuerySdkForBalances(addr sdk.AccAddress) sdk.Coins {
res, err := chain.Bank.AllBalances(context.Background(), &banktypes.QueryAllBalancesRequest{ res, err := chain.Bank.AllBalances(context.Background(), &banktypes.QueryAllBalancesRequest{

View File

@ -110,7 +110,7 @@ func ParseLiveNetworkConfig() LiveNetworkConfig {
return LiveNetworkConfig{ return LiveNetworkConfig{
KavaRpcUrl: nonemptyStringEnv("E2E_KAVA_RPC_URL"), KavaRpcUrl: nonemptyStringEnv("E2E_KAVA_RPC_URL"),
KavaGrpcUrl: nonemptyStringEnv("E2E_KAVA_GRPC_URL"), KavaGrpcUrl: nonemptyStringEnv("E2E_KAVA_GRPC_URL"),
KavaEvmRpcUrl: nonemptyStringEnv("E2E_KAVA_EMV_RPC_URL"), KavaEvmRpcUrl: nonemptyStringEnv("E2E_KAVA_EVM_RPC_URL"),
} }
} }

View File

@ -6,10 +6,8 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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/e2e/contracts/greeter"
"github.com/kava-labs/kava/tests/util"
"github.com/kava-labs/kava/x/earn/types" "github.com/kava-labs/kava/x/earn/types"
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" evmutiltypes "github.com/kava-labs/kava/x/evmutil/types"
) )
@ -41,6 +39,7 @@ func (suite *E2eTestSuite) InitKavaEvmData() {
if !found { if !found {
panic(fmt.Sprintf("erc20 %s must be enabled for conversion to cosmos coin", erc20Addr)) panic(fmt.Sprintf("erc20 %s must be enabled for conversion to cosmos coin", erc20Addr))
} }
suite.Kava.RegisterErc20(suite.DeployedErc20.Address)
// expect the erc20's cosmos denom to be a supported earn vault // expect the erc20's cosmos denom to be a supported earn vault
_, err = suite.Kava.Earn.Vault( _, err = suite.Kava.Earn.Vault(
@ -65,15 +64,7 @@ func (suite *E2eTestSuite) InitKavaEvmData() {
func (suite *E2eTestSuite) FundKavaErc20Balance(toAddress common.Address, amount *big.Int) EvmTxResponse { func (suite *E2eTestSuite) FundKavaErc20Balance(toAddress common.Address, amount *big.Int) EvmTxResponse {
// funded account should have erc20 balance // funded account should have erc20 balance
whale := suite.Kava.GetAccount(FundedAccountName) whale := suite.Kava.GetAccount(FundedAccountName)
res, err := whale.TransferErc20(suite.DeployedErc20.Address, toAddress, amount)
data := util.BuildErc20TransferCallData(toAddress, amount)
nonce, err := suite.Kava.EvmClient.PendingNonceAt(context.Background(), whale.EvmAddress)
suite.NoError(err) suite.NoError(err)
return res
req := util.EvmTxRequest{
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()),
}
return whale.SignAndBroadcastEvmTx(req)
} }

View File

@ -8,8 +8,12 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/tests/e2e/runner" "github.com/kava-labs/kava/tests/e2e/runner"
"github.com/kava-labs/kava/tests/util"
) )
const ( const (
@ -48,6 +52,31 @@ type E2eTestSuite struct {
UpgradeHeight int64 UpgradeHeight int64
DeployedErc20 DeployedErc20 DeployedErc20 DeployedErc20
cost costSummary
enableRefunds bool
}
// costSummary wraps info about what funds get irrecoverably spent by the test suite run
type costSummary struct {
sdkAddress string
evmAddress string
erc20BalanceBefore *big.Int
erc20BalanceAfter *big.Int
sdkBalanceBefore sdk.Coins
sdkBalanceAfter sdk.Coins
}
// String implements fmt.Stringer
func (s costSummary) String() string {
before := sdk.NewCoins(s.sdkBalanceBefore...).Add(sdk.NewCoin("erc20", sdkmath.NewIntFromBigInt(s.erc20BalanceBefore)))
after := sdk.NewCoins(s.sdkBalanceAfter...).Add(sdk.NewCoin("erc20", sdkmath.NewIntFromBigInt(s.erc20BalanceAfter)))
cost, _ := before.SafeSub(after...)
return fmt.Sprintf("Cost Summary for %s (%s):\nbefore:\n%s\nafter:\n%s\ncost:\n%s\n",
s.sdkAddress, s.evmAddress, util.PrettyPrintCoins(before), util.PrettyPrintCoins(after), util.PrettyPrintCoins(cost),
)
} }
// SetupSuite is run before all tests. It initializes chain connections and sets up the // SetupSuite is run before all tests. It initializes chain connections and sets up the
@ -91,6 +120,14 @@ func (suite *E2eTestSuite) SetupSuite() {
} }
suite.InitKavaEvmData() suite.InitKavaEvmData()
whale := suite.Kava.GetAccount(FundedAccountName)
suite.cost = costSummary{
sdkAddress: whale.SdkAddress.String(),
evmAddress: whale.EvmAddress.Hex(),
sdkBalanceBefore: suite.Kava.QuerySdkForBalances(whale.SdkAddress),
erc20BalanceBefore: suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, whale.EvmAddress),
}
} }
// TearDownSuite is run after all tests have run. // TearDownSuite is run after all tests have run.
@ -98,6 +135,25 @@ func (suite *E2eTestSuite) SetupSuite() {
func (suite *E2eTestSuite) TearDownSuite() { func (suite *E2eTestSuite) TearDownSuite() {
fmt.Println("tearing down test suite.") fmt.Println("tearing down test suite.")
whale := suite.Kava.GetAccount(FundedAccountName)
if suite.enableRefunds {
suite.cost.sdkBalanceAfter = suite.Kava.QuerySdkForBalances(whale.SdkAddress)
suite.cost.erc20BalanceAfter = suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, whale.EvmAddress)
fmt.Println("==BEFORE REFUNDS==")
fmt.Println(suite.cost)
fmt.Println("attempting to return all unused funds")
suite.Kava.ReturnAllFunds()
fmt.Println("==AFTER REFUNDS==")
}
// calculate & output cost summary for funded account
suite.cost.sdkBalanceAfter = suite.Kava.QuerySdkForBalances(whale.SdkAddress)
suite.cost.erc20BalanceAfter = suite.Kava.GetErc20Balance(suite.DeployedErc20.Address, whale.EvmAddress)
fmt.Println(suite.cost)
// TODO: track asset denoms & then return all funds to initial funding account. // TODO: track asset denoms & then return all funds to initial funding account.
// close all account request channels // close all account request channels
@ -113,6 +169,7 @@ func (suite *E2eTestSuite) TearDownSuite() {
func (suite *E2eTestSuite) SetupKvtoolNodeRunner() *runner.KvtoolRunner { func (suite *E2eTestSuite) SetupKvtoolNodeRunner() *runner.KvtoolRunner {
// upgrade tests are only supported on kvtool networks // upgrade tests are only supported on kvtool networks
suite.UpgradeHeight = suite.config.Kvtool.KavaUpgradeHeight suite.UpgradeHeight = suite.config.Kvtool.KavaUpgradeHeight
suite.enableRefunds = false
runnerConfig := runner.KvtoolRunnerConfig{ runnerConfig := runner.KvtoolRunnerConfig{
KavaConfigTemplate: suite.config.Kvtool.KavaConfigTemplate, KavaConfigTemplate: suite.config.Kvtool.KavaConfigTemplate,
@ -137,6 +194,7 @@ func (suite *E2eTestSuite) SetupLiveNetworkNodeRunner() *runner.LiveNodeRunner {
if suite.config.IncludeIbcTests { if suite.config.IncludeIbcTests {
panic("ibc tests not supported for live network configuration") panic("ibc tests not supported for live network configuration")
} }
suite.enableRefunds = true
runnerConfig := runner.LiveNodeRunnerConfig{ runnerConfig := runner.LiveNodeRunnerConfig{
KavaRpcUrl: suite.config.LiveNetwork.KavaRpcUrl, KavaRpcUrl: suite.config.LiveNetwork.KavaRpcUrl,

20
tests/util/strings.go Normal file
View File

@ -0,0 +1,20 @@
package util
import (
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func PrettyPrintCoins(coins sdk.Coins) string {
if len(coins) == 0 {
return ""
}
out := make([]string, 0, len(coins))
for _, coin := range coins {
out = append(out, coin.String())
}
return fmt.Sprintf("- %s", strings.Join(out, "\n- "))
}