diff --git a/.gitignore b/.gitignore index 017d95a6..a2a2088c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ build/bin # Ignore build dirs build/darwin build/linux + +# Go workspace files +go.work +go.work.sum diff --git a/tests/e2e/e2e_convert_cosmos_coins_test.go b/tests/e2e/e2e_convert_cosmos_coins_test.go new file mode 100644 index 00000000..c8271998 --- /dev/null +++ b/tests/e2e/e2e_convert_cosmos_coins_test.go @@ -0,0 +1,244 @@ +package e2e_test + +import ( + "context" + "math/big" + "time" + + sdkerrors "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/ethereum/go-ethereum" + "github.com/kava-labs/kava/tests/e2e/testutil" + "github.com/kava-labs/kava/tests/util" + evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" +) + +func setupConvertToCoinTest( + suite *IntegrationTestSuite, accountName string, +) (denom string, initialFunds sdk.Coins, user *testutil.SigningAccount) { + // we expect a denom to be registered to the allowed denoms param + // and for the funded account to have a balance for that denom + params, err := suite.Kava.Evmutil.Params(context.Background(), &evmutiltypes.QueryParamsRequest{}) + suite.NoError(err) + suite.GreaterOrEqual( + len(params.Params.AllowedCosmosDenoms), 1, + "kava chain expected to have at least one AllowedCosmosDenom for ERC20 conversion", + ) + + tokenInfo := params.Params.AllowedCosmosDenoms[0] + denom = tokenInfo.CosmosDenom + initialFunds = sdk.NewCoins( + sdk.NewInt64Coin(suite.Kava.StakingDenom, 1e6), // gas money + sdk.NewInt64Coin(denom, 1e10), // conversion-enabled cosmos coin + ) + + user = suite.Kava.NewFundedAccount(accountName, initialFunds) + + return denom, initialFunds, user +} + +func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() { + denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter") + + fee := sdk.NewCoins(ukava(7500)) + convertAmount := int64(5e9) + initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) + + /////////////////////////////// + // CONVERT COSMOS COIN -> ERC20 + /////////////////////////////// + convertToErc20Msg := evmutiltypes.NewMsgConvertCosmosCoinToERC20( + user.SdkAddress.String(), + user.EvmAddress.Hex(), + sdk.NewInt64Coin(denom, convertAmount), + ) + tx := util.KavaMsgRequest{ + Msgs: []sdk.Msg{&convertToErc20Msg}, + GasLimit: 2e6, + FeeAmount: fee, + Data: "converting sdk coin to erc20", + } + res := user.SignAndBroadcastKavaTx(tx) + suite.NoError(res.Err) + + // query for the deployed contract + deployedContracts, err := suite.Kava.Evmutil.DeployedCosmosCoinContracts( + context.Background(), + &evmutiltypes.QueryDeployedCosmosCoinContractsRequest{CosmosDenoms: []string{denom}}, + ) + suite.NoError(err) + suite.Len(deployedContracts.DeployedCosmosCoinContracts, 1) + + contractAddress := deployedContracts.DeployedCosmosCoinContracts[0].Address + + // check erc20 balance + bz, err := suite.Kava.EvmClient.CallContract(context.Background(), ethereum.CallMsg{ + To: &contractAddress.Address, + Data: util.BuildErc20BalanceOfCallData(user.EvmAddress), + }, nil) + suite.NoError(err) + erc20Balance := new(big.Int).SetBytes(bz) + suite.BigIntsEqual(big.NewInt(convertAmount), erc20Balance, "unexpected erc20 balance post-convert") + + // check cosmos coin is deducted from account + expectedFunds := initialFunds.AmountOf(denom).SubRaw(convertAmount) + balance := suite.Kava.QuerySdkForBalances(user.SdkAddress).AmountOf(denom) + suite.Equal(expectedFunds, balance) + + // check that module account has sdk coins + expectedModuleBalance := initialModuleBalance.AddRaw(convertAmount) + actualModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) + suite.Equal(expectedModuleBalance, actualModuleBalance) + + /////////////////////////////// + // CONVERT ERC20 -> COSMOS COIN + /////////////////////////////// + convertFromErc20Msg := evmutiltypes.NewMsgConvertCosmosCoinFromERC20( + user.EvmAddress.Hex(), + user.SdkAddress.String(), + sdk.NewInt64Coin(denom, convertAmount), + ) + + tx = util.KavaMsgRequest{ + Msgs: []sdk.Msg{&convertFromErc20Msg}, + GasLimit: 2e5, + FeeAmount: fee, + Data: "converting erc20 to cosmos coin", + } + res = user.SignAndBroadcastKavaTx(tx) + suite.NoError(res.Err) + + // check erc20 balance + bz, err = suite.Kava.EvmClient.CallContract(context.Background(), ethereum.CallMsg{ + To: &contractAddress.Address, + Data: util.BuildErc20BalanceOfCallData(user.EvmAddress), + }, nil) + suite.NoError(err) + erc20Balance = new(big.Int).SetBytes(bz) + suite.BigIntsEqual(big.NewInt(0), erc20Balance, "expected all erc20 to be converted back") + + // check cosmos coin is added back to account + expectedFunds = initialFunds.AmountOf(denom) + balance = suite.Kava.QuerySdkForBalances(user.SdkAddress).AmountOf(denom) + suite.Equal(expectedFunds, balance) + + // check that module account has sdk coins deducted + actualModuleBalance = suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) + suite.Equal(initialModuleBalance, actualModuleBalance) +} + +// like the above, but confirming we can sign the messages with eip712 +func (suite *IntegrationTestSuite) TestEIP712ConvertCosmosCoinsToFromERC20() { + denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter-eip712") + + convertAmount := int64(5e9) + initialModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) + + /////////////////////////////// + // CONVERT COSMOS COIN -> ERC20 + /////////////////////////////// + convertToErc20Msg := evmutiltypes.NewMsgConvertCosmosCoinToERC20( + user.SdkAddress.String(), + user.EvmAddress.Hex(), + sdk.NewInt64Coin(denom, convertAmount), + ) + tx := suite.NewEip712TxBuilder( + user, + suite.Kava, + 2e6, + sdk.NewCoins(ukava(1e4)), + []sdk.Msg{&convertToErc20Msg}, + "this is a memo", + ).GetTx() + txBytes, err := suite.Kava.EncodingConfig.TxConfig.TxEncoder()(tx) + suite.NoError(err) + + // submit the eip712 message to the chain. + 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) + + // query for the deployed contract + deployedContracts, err := suite.Kava.Evmutil.DeployedCosmosCoinContracts( + context.Background(), + &evmutiltypes.QueryDeployedCosmosCoinContractsRequest{CosmosDenoms: []string{denom}}, + ) + suite.NoError(err) + suite.Len(deployedContracts.DeployedCosmosCoinContracts, 1) + + contractAddress := deployedContracts.DeployedCosmosCoinContracts[0].Address + + // check erc20 balance + bz, err := suite.Kava.EvmClient.CallContract(context.Background(), ethereum.CallMsg{ + To: &contractAddress.Address, + Data: util.BuildErc20BalanceOfCallData(user.EvmAddress), + }, nil) + suite.NoError(err) + erc20Balance := new(big.Int).SetBytes(bz) + suite.BigIntsEqual(big.NewInt(convertAmount), erc20Balance, "unexpected erc20 balance post-convert") + + // check cosmos coin is deducted from account + expectedFunds := initialFunds.AmountOf(denom).SubRaw(convertAmount) + balance := suite.Kava.QuerySdkForBalances(user.SdkAddress).AmountOf(denom) + suite.Equal(expectedFunds, balance) + + // check that module account has sdk coins + expectedModuleBalance := initialModuleBalance.AddRaw(convertAmount) + actualModuleBalance := suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) + suite.Equal(expectedModuleBalance, actualModuleBalance) + + /////////////////////////////// + // CONVERT ERC20 -> COSMOS COIN + /////////////////////////////// + convertFromErc20Msg := evmutiltypes.NewMsgConvertCosmosCoinFromERC20( + user.EvmAddress.Hex(), + user.SdkAddress.String(), + sdk.NewInt64Coin(denom, convertAmount), + ) + tx = suite.NewEip712TxBuilder( + user, + suite.Kava, + 2e5, + sdk.NewCoins(ukava(1e4)), + []sdk.Msg{&convertFromErc20Msg}, + "", + ).GetTx() + txBytes, err = suite.Kava.EncodingConfig.TxConfig.TxEncoder()(tx) + suite.NoError(err) + + // submit the eip712 message to the chain + 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 erc20 balance + bz, err = suite.Kava.EvmClient.CallContract(context.Background(), ethereum.CallMsg{ + To: &contractAddress.Address, + Data: util.BuildErc20BalanceOfCallData(user.EvmAddress), + }, nil) + suite.NoError(err) + erc20Balance = new(big.Int).SetBytes(bz) + suite.BigIntsEqual(big.NewInt(0), erc20Balance, "expected all erc20 to be converted back") + + // check cosmos coin is added back to account + expectedFunds = initialFunds.AmountOf(denom) + balance = suite.Kava.QuerySdkForBalances(user.SdkAddress).AmountOf(denom) + suite.Equal(expectedFunds, balance) + + // check that module account has sdk coins deducted + actualModuleBalance = suite.Kava.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom) + suite.Equal(initialModuleBalance, actualModuleBalance) +} diff --git a/tests/e2e/e2e_upgrade_handler_test.go b/tests/e2e/e2e_upgrade_handler_test.go index f7c8d8b7..a081efd1 100644 --- a/tests/e2e/e2e_upgrade_handler_test.go +++ b/tests/e2e/e2e_upgrade_handler_test.go @@ -5,7 +5,7 @@ import "fmt" // TestUpgradeHandler can be used to run tests post-upgrade. If an upgrade is enabled, all tests // are run against the upgraded chain. However, this file is a good place to consolidate all // acceptance tests for a given set of upgrade handlers. -func (suite IntegrationTestSuite) TestUpgradeHandler() { +func (suite *IntegrationTestSuite) TestUpgradeHandler() { suite.SkipIfUpgradeDisabled() fmt.Println("An upgrade has run!") suite.True(true) diff --git a/tests/e2e/kvtool b/tests/e2e/kvtool index ea6bd538..98d25c9b 160000 --- a/tests/e2e/kvtool +++ b/tests/e2e/kvtool @@ -1 +1 @@ -Subproject commit ea6bd5386c80d372c52f0801ff91923eb0dab56f +Subproject commit 98d25c9b659eaee465c72198f387268ce763400c diff --git a/tests/e2e/testutil/chain.go b/tests/e2e/testutil/chain.go index d1d8f453..a9f35dca 100644 --- a/tests/e2e/testutil/chain.go +++ b/tests/e2e/testutil/chain.go @@ -22,6 +22,7 @@ import ( "github.com/kava-labs/kava/tests/util" communitytypes "github.com/kava-labs/kava/x/community/types" earntypes "github.com/kava-labs/kava/x/earn/types" + evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" ) // Chain wraps query clients & accounts for a network @@ -42,6 +43,7 @@ type Chain struct { Community communitytypes.QueryClient Earn earntypes.QueryClient Evm evmtypes.QueryClient + Evmutil evmutiltypes.QueryClient Tm tmservice.ServiceClient Tx txtypes.ServiceClient } @@ -75,6 +77,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic chain.Community = communitytypes.NewQueryClient(grpcConn) chain.Earn = earntypes.NewQueryClient(grpcConn) chain.Evm = evmtypes.NewQueryClient(grpcConn) + chain.Evmutil = evmutiltypes.NewQueryClient(grpcConn) chain.Tm = tmservice.NewServiceClient(grpcConn) chain.Tx = txtypes.NewServiceClient(grpcConn) @@ -114,3 +117,9 @@ func (chain *Chain) QuerySdkForBalances(addr sdk.AccAddress) sdk.Coins { require.NoError(chain.t, err) return res.Balances } + +// GetModuleBalances returns the balance of a requested module account +func (chain *Chain) GetModuleBalances(moduleName string) sdk.Coins { + addr := authtypes.NewModuleAddress(moduleName) + return chain.QuerySdkForBalances(addr) +}