0g-chain/tests/e2e/e2e_convert_cosmos_coins_test.go
2024-08-02 12:59:25 +08:00

442 lines
16 KiB
Go

package e2e_test
import (
"context"
"fmt"
"math/big"
"time"
sdkerrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/0glabs/0g-chain/chaincfg"
"github.com/0glabs/0g-chain/tests/e2e/testutil"
"github.com/0glabs/0g-chain/tests/util"
evmutiltypes "github.com/0glabs/0g-chain/x/evmutil/types"
)
const initialCosmosCoinConversionDenomFunds = int64(1e4)
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.ZgChain.Evmutil.Params(context.Background(), &evmutiltypes.QueryParamsRequest{})
suite.NoError(err)
suite.GreaterOrEqual(
len(params.Params.AllowedCosmosDenoms), 1,
"0g-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.ZgChain.StakingDenom, 1e5), // gas money
sdk.NewInt64Coin(denom, initialCosmosCoinConversionDenomFunds), // conversion-enabled cosmos coin
)
user = suite.ZgChain.NewFundedAccount(accountName, initialFunds)
return denom, initialFunds, user
}
// amount must be less than initial funds (initialCosmosCoinConversionDenomFunds)
func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance(
accountName string, amount int64,
) (user *testutil.SigningAccount, contractAddress *evmutiltypes.InternalEVMAddress, denom string, sdkBalance sdk.Coins) {
if amount > initialCosmosCoinConversionDenomFunds {
panic(fmt.Sprintf("test erc20 amount must be less than %d", initialCosmosCoinConversionDenomFunds))
}
denom, sdkBalance, user = setupConvertToCoinTest(suite, accountName)
convertAmount := sdk.NewInt64Coin(denom, amount)
// setup user to have erc20 balance
msg := evmutiltypes.NewMsgConvertCosmosCoinToERC20(
user.SdkAddress.String(),
user.EvmAddress.Hex(),
convertAmount,
)
tx := util.ZgChainMsgRequest{
Msgs: []sdk.Msg{&msg},
GasLimit: 4e5,
FeeAmount: sdk.NewCoins(chaincfg.MakeCoinForGasDenom(400)),
Data: "converting sdk coin to erc20",
}
res := user.SignAndBroadcastZgChainTx(tx)
suite.NoError(res.Err)
// adjust sdk balance
sdkBalance = sdkBalance.Sub(convertAmount)
// query for the deployed contract
deployedContracts, err := suite.ZgChain.Evmutil.DeployedCosmosCoinContracts(
context.Background(),
&evmutiltypes.QueryDeployedCosmosCoinContractsRequest{CosmosDenoms: []string{denom}},
)
suite.NoError(err)
suite.Len(deployedContracts.DeployedCosmosCoinContracts, 1)
contractAddress = deployedContracts.DeployedCosmosCoinContracts[0].Address
return user, contractAddress, denom, sdkBalance
}
func (suite *IntegrationTestSuite) TestConvertCosmosCoinsToFromERC20() {
denom, initialFunds, user := setupConvertToCoinTest(suite, "cosmo-coin-converter")
convertAmount := int64(5e3)
initialModuleBalance := suite.ZgChain.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom)
///////////////////////////////
// CONVERT COSMOS COIN -> ERC20
///////////////////////////////
convertToErc20Msg := evmutiltypes.NewMsgConvertCosmosCoinToERC20(
user.SdkAddress.String(),
user.EvmAddress.Hex(),
sdk.NewInt64Coin(denom, convertAmount),
)
tx := util.ZgChainMsgRequest{
Msgs: []sdk.Msg{&convertToErc20Msg},
GasLimit: 2e6,
FeeAmount: sdk.NewCoins(chaincfg.MakeCoinForGasDenom(2000)),
Data: "converting sdk coin to erc20",
}
res := user.SignAndBroadcastZgChainTx(tx)
suite.NoError(res.Err)
// query for the deployed contract
deployedContracts, err := suite.ZgChain.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
erc20Balance := suite.ZgChain.GetErc20Balance(contractAddress.Address, user.EvmAddress)
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.ZgChain.QuerySdkForBalances(user.SdkAddress).AmountOf(denom)
suite.Equal(expectedFunds, balance)
// check that module account has sdk coins
expectedModuleBalance := initialModuleBalance.AddRaw(convertAmount)
actualModuleBalance := suite.ZgChain.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.ZgChainMsgRequest{
Msgs: []sdk.Msg{&convertFromErc20Msg},
GasLimit: 2e5,
FeeAmount: sdk.NewCoins(chaincfg.MakeCoinForGasDenom(200)),
Data: "converting erc20 to cosmos coin",
}
res = user.SignAndBroadcastZgChainTx(tx)
suite.NoError(res.Err)
// check erc20 balance
erc20Balance = suite.ZgChain.GetErc20Balance(contractAddress.Address, user.EvmAddress)
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.ZgChain.QuerySdkForBalances(user.SdkAddress).AmountOf(denom)
suite.Equal(expectedFunds, balance)
// check that module account has sdk coins deducted
actualModuleBalance = suite.ZgChain.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(5e3)
initialModuleBalance := suite.ZgChain.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.ZgChain,
2e6,
sdk.NewCoins(chaincfg.MakeCoinForGasDenom(1e4)),
[]sdk.Msg{&convertToErc20Msg},
"this is a memo",
).GetTx()
txBytes, err := suite.ZgChain.EncodingConfig.TxConfig.TxEncoder()(tx)
suite.NoError(err)
// submit the eip712 message to the chain.
res, err := suite.ZgChain.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.ZgChain.Tx, res.TxResponse.TxHash, 12*time.Second)
suite.Require().NoError(err)
// query for the deployed contract
deployedContracts, err := suite.ZgChain.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
erc20Balance := suite.ZgChain.GetErc20Balance(contractAddress.Address, user.EvmAddress)
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.ZgChain.QuerySdkForBalances(user.SdkAddress).AmountOf(denom)
suite.Equal(expectedFunds, balance)
// check that module account has sdk coins
expectedModuleBalance := initialModuleBalance.AddRaw(convertAmount)
actualModuleBalance := suite.ZgChain.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.ZgChain,
2e5,
sdk.NewCoins(chaincfg.MakeCoinForGasDenom(200)),
[]sdk.Msg{&convertFromErc20Msg},
"",
).GetTx()
txBytes, err = suite.ZgChain.EncodingConfig.TxConfig.TxEncoder()(tx)
suite.NoError(err)
// submit the eip712 message to the chain
res, err = suite.ZgChain.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.ZgChain.Tx, res.TxResponse.TxHash, 6*time.Second)
suite.NoError(err)
// check erc20 balance
erc20Balance = suite.ZgChain.GetErc20Balance(contractAddress.Address, user.EvmAddress)
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.ZgChain.QuerySdkForBalances(user.SdkAddress).AmountOf(denom)
suite.Equal(expectedFunds, balance)
// check that module account has sdk coins deducted
actualModuleBalance = suite.ZgChain.GetModuleBalances(evmutiltypes.ModuleName).AmountOf(denom)
suite.Equal(initialModuleBalance, actualModuleBalance)
}
func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ForbiddenERC20Calls() {
// 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() {
data := util.BuildErc20MintCallData(user.EvmAddress, big.NewInt(1))
nonce, err := user.NextNonce()
suite.NoError(err)
mintTx := util.EvmTxRequest{
Tx: ethtypes.NewTx(
&ethtypes.LegacyTx{
Nonce: nonce,
GasPrice: minEvmGasPrice,
Gas: 1e5,
To: &contractAddress.Address,
Data: data,
},
),
Data: "attempting to mint, should fail",
}
res := user.SignAndBroadcastEvmTx(mintTx)
suite.ErrorAs(res.Err, &util.ErrEvmTxFailed)
// TODO: when traceTransactions enabled, read actual error log
suite.ErrorContains(res.Err, "transaction was committed but failed. likely an execution revert by contract code")
})
suite.Run("users can't burn()", func() {
data := util.BuildErc20BurnCallData(user.EvmAddress, big.NewInt(1))
nonce, err := user.NextNonce()
suite.NoError(err)
burnTx := util.EvmTxRequest{
Tx: ethtypes.NewTx(
&ethtypes.LegacyTx{
Nonce: nonce,
GasPrice: minEvmGasPrice,
Gas: 1e5,
To: &contractAddress.Address,
Data: data,
},
),
Data: "attempting to burn, should fail",
}
res := user.SignAndBroadcastEvmTx(burnTx)
suite.ErrorAs(res.Err, &util.ErrEvmTxFailed)
// TODO: when traceTransactions enabled, read actual error log
suite.ErrorContains(res.Err, "transaction was committed but failed. likely an execution revert by contract code")
})
}
// - 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
func (suite *IntegrationTestSuite) TestConvertCosmosCoins_ERC20Magic() {
initialAliceAmount := int64(2e3)
alice, contractAddress, denom, _ := suite.setupAccountWithCosmosCoinERC20Balance(
"cosmo-coin-converter-complex-alice", initialAliceAmount,
)
gasMoney := sdk.NewCoins(chaincfg.MakeCoinForGasDenom(1e5))
bob := suite.ZgChain.NewFundedAccount("cosmo-coin-converter-complex-bob", gasMoney)
amount := big.NewInt(1e3) // test assumes this is half of alice's balance.
// bob can't move alice's funds
nonce, err := bob.NextNonce()
suite.NoError(err)
transferFromTxData := &ethtypes.LegacyTx{
Nonce: nonce,
GasPrice: minEvmGasPrice,
Gas: 1e5,
To: &contractAddress.Address,
Value: &big.Int{},
Data: util.BuildErc20TransferFromCallData(alice.EvmAddress, bob.EvmAddress, amount),
}
transferTx := util.EvmTxRequest{
Tx: ethtypes.NewTx(transferFromTxData),
Data: "bob can't move alice's funds, should fail",
}
res := bob.SignAndBroadcastEvmTx(transferTx)
suite.ErrorAs(res.Err, &util.ErrEvmTxFailed)
suite.ErrorContains(res.Err, "transaction was committed but failed. likely an execution revert by contract code")
// approve bob to move alice's funds
nonce, err = alice.NextNonce()
suite.NoError(err)
approveTx := util.EvmTxRequest{
Tx: ethtypes.NewTx(&ethtypes.LegacyTx{
Nonce: nonce,
GasPrice: minEvmGasPrice,
Gas: 1e5,
To: &contractAddress.Address,
Value: &big.Int{},
Data: util.BuildErc20ApproveCallData(bob.EvmAddress, amount),
}),
Data: "alice approves bob to spend amount",
}
res = alice.SignAndBroadcastEvmTx(approveTx)
suite.NoError(res.Err)
// bob can't move more than alice allowed
transferFromTxData.Data = util.BuildErc20TransferFromCallData(
alice.EvmAddress, bob.EvmAddress, new(big.Int).Add(amount, big.NewInt(1)),
)
transferFromTxData.Nonce, err = bob.NextNonce()
suite.NoError(err)
transferTooMuchTx := util.EvmTxRequest{
Tx: ethtypes.NewTx(transferFromTxData),
Data: "transferring more than approved, should fail",
}
res = bob.SignAndBroadcastEvmTx(transferTooMuchTx)
suite.ErrorAs(res.Err, &util.ErrEvmTxFailed)
suite.ErrorContains(res.Err, "transaction was committed but failed. likely an execution revert by contract code")
// bob can move allowed amount
transferFromTxData.Data = util.BuildErc20TransferFromCallData(alice.EvmAddress, bob.EvmAddress, amount)
transferFromTxData.Nonce, err = bob.NextNonce()
suite.NoError(err)
transferJustRightTx := util.EvmTxRequest{
Tx: ethtypes.NewTx(transferFromTxData),
Data: "bob transfers alice's funds, allowed because he's approved",
}
res = bob.SignAndBroadcastEvmTx(transferJustRightTx)
suite.Require().NoError(res.Err)
// alice should have amount deducted
erc20Balance := suite.ZgChain.GetErc20Balance(contractAddress.Address, alice.EvmAddress)
suite.BigIntsEqual(big.NewInt(initialAliceAmount-amount.Int64()), erc20Balance, "alice has unexpected erc20 balance")
// bob should have amount added
erc20Balance = suite.ZgChain.GetErc20Balance(contractAddress.Address, bob.EvmAddress)
suite.BigIntsEqual(amount, erc20Balance, "bob has unexpected erc20 balance")
// convert bob's new funds back to an sdk.Coin
convertMsg := evmutiltypes.NewMsgConvertCosmosCoinFromERC20(
bob.EvmAddress.Hex(),
bob.SdkAddress.String(),
sdk.NewInt64Coin(denom, amount.Int64()),
)
convertTx := util.ZgChainMsgRequest{
Msgs: []sdk.Msg{&convertMsg},
GasLimit: 2e5,
FeeAmount: sdk.NewCoins(chaincfg.MakeCoinForGasDenom(200)),
Data: "bob converts his new erc20 to an sdk.Coin",
}
convertRes := bob.SignAndBroadcastZgChainTx(convertTx)
suite.NoError(convertRes.Err)
// bob should have no more erc20 balance
erc20Balance = suite.ZgChain.GetErc20Balance(contractAddress.Address, bob.EvmAddress)
suite.BigIntsEqual(big.NewInt(0), erc20Balance, "expected no erc20 balance for bob")
// bob should have sdk balance
balance := suite.ZgChain.QuerySdkForBalances(bob.SdkAddress).AmountOf(denom)
suite.Equal(sdk.NewIntFromBigInt(amount), balance)
// alice should have the remaining balance
erc20Balance = suite.ZgChain.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.SignAndBroadcastZgChainTx(convertTx)
suite.NoError(convertRes.Err)
}