mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-27 08:45:19 +00:00
444 lines
16 KiB
Go
444 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/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.Grpc.Query.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(a0gi(big.NewInt(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.Grpc.Query.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(a0gi(big.NewInt(2000))),
|
|
Data: "converting sdk coin to erc20",
|
|
}
|
|
res := user.SignAndBroadcastZgChainTx(tx)
|
|
suite.NoError(res.Err)
|
|
|
|
// query for the deployed contract
|
|
deployedContracts, err := suite.ZgChain.Grpc.Query.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(a0gi(big.NewInt(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(a0gi(big.NewInt(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.Grpc.Query.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.Grpc.Query.Tx, res.TxResponse.TxHash, 12*time.Second)
|
|
suite.Require().NoError(err)
|
|
|
|
// query for the deployed contract
|
|
deployedContracts, err := suite.ZgChain.Grpc.Query.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(a0gi(big.NewInt(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.Grpc.Query.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.Grpc.Query.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(
|
|
ðtypes.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(
|
|
ðtypes.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(a0gi(big.NewInt(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 := ðtypes.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(ðtypes.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(a0gi(big.NewInt(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)
|
|
}
|