Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests(erc20): Add ERC20 integration test setup #2080

Merged
merged 10 commits into from
Nov 27, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (erc20) [#2073](https://github.com/evmos/evmos/pull/2073) Align metadata query errors with ERC20 contracts.
- (werc20) [#2074](https://github.com/evmos/evmos/pull/2074) Add `werc20` EVM Extension acceptance tests with `WEVMOS` contract.
- (erc20) [#2075](https://github.com/evmos/evmos/pull/2075) Align allowance adjustment errors with ERC20 contracts.
- (erc20) [#2080](https://github.com/evmos/evmos/pull/2080) Add ERC20 integration test setup.

### Bug Fixes

Expand Down
326 changes: 326 additions & 0 deletions precompiles/erc20/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
package erc20_test

import (
"strings"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/evmos/evmos/v15/contracts"
"github.com/evmos/evmos/v15/precompiles/erc20"
"github.com/evmos/evmos/v15/precompiles/erc20/testdata"
"github.com/evmos/evmos/v15/precompiles/testutil"
"github.com/evmos/evmos/v15/testutil/integration/evmos/factory"
"github.com/evmos/evmos/v15/testutil/integration/evmos/grpc"
"github.com/evmos/evmos/v15/testutil/integration/evmos/keyring"
"github.com/evmos/evmos/v15/testutil/integration/evmos/network"
"github.com/evmos/evmos/v15/testutil/integration/evmos/utils"
evmtypes "github.com/evmos/evmos/v15/x/evm/types"

//nolint:revive // dot imports are fine for Ginkgo
. "github.com/onsi/ginkgo/v2"
//nolint:revive // dot imports are fine for Ginkgo
. "github.com/onsi/gomega"
)

var is *IntegrationTestSuite

type IntegrationTestSuite struct {
// NOTE: we have to use the Unit testing network because we access a keeper in a setup function.
// Might adjust this on a follow-up PR.
network *network.UnitTestNetwork
handler grpc.Handler
keyring keyring.Keyring
factory factory.TxFactory

bondDenom string
tokenDenom string

precompile *erc20.Precompile
}

func (is *IntegrationTestSuite) SetupTest() {
keys := keyring.New(2)
nw := network.NewUnitTestNetwork(
network.WithPreFundedAccounts(keys.GetAllAccAddrs()...),
)
gh := grpc.NewIntegrationHandler(nw)
tf := factory.New(nw, gh)

// Set up min deposit in Evmos
params, err := gh.GetGovParams("deposit")
Expect(err).ToNot(HaveOccurred(), "failed to get gov params")
Expect(params).ToNot(BeNil(), "returned gov params are nil")

updatedParams := params.Params
updatedParams.MinDeposit = sdk.NewCoins(sdk.NewCoin(nw.GetDenom(), sdk.NewInt(1e18)))
err = nw.UpdateGovParams(*updatedParams)
Expect(err).ToNot(HaveOccurred(), "failed to update the min deposit")

is.network = nw
is.factory = tf
is.handler = gh
is.keyring = keys

is.bondDenom = nw.GetDenom()
is.tokenDenom = "xmpl"

is.precompile = is.setupERC20Precompile(is.tokenDenom)
}

func TestIntegrationSuite(t *testing.T) {
is = new(IntegrationTestSuite)

// Run Ginkgo integration tests
RegisterFailHandler(Fail)
RunSpecs(t, "ERC20 Extension Suite")
}

var _ = Describe("ERC20 Extension -", func() {
var (
// contractsData holds the addresses and ABIs for the different
// contract instances that are subject to testing here.
contractsData ContractsData

execRevertedCheck testutil.LogCheckArgs
failCheck testutil.LogCheckArgs
passCheck testutil.LogCheckArgs
)

BeforeEach(func() {
is.SetupTest()

sender := is.keyring.GetKey(0)
contractAddr, err := is.factory.DeployContract(
sender.Priv,
evmtypes.EvmTxArgs{}, // NOTE: passing empty struct to use default values
factory.ContractDeploymentData{
Contract: testdata.ERC20AllowanceCallerContract,
// NOTE: we're passing the precompile address to the constructor because that initiates the contract
// to make calls to the correct ERC20 precompile.
ConstructorArgs: []interface{}{is.precompile.Address()},
},
)
Expect(err).ToNot(HaveOccurred(), "failed to deploy contract")

erc20MinterBurnerAddr, err := is.factory.DeployContract(
sender.Priv,
evmtypes.EvmTxArgs{}, // NOTE: passing empty struct to use default values
factory.ContractDeploymentData{
Contract: contracts.ERC20MinterBurnerDecimalsContract,
ConstructorArgs: []interface{}{
"Xmpl", "Xmpl", uint8(6),
},
},
)
Expect(err).ToNot(HaveOccurred(), "failed to deploy ERC20 minter burner contract")

ERC20MinterV5Addr, err := is.factory.DeployContract(
sender.Priv,
evmtypes.EvmTxArgs{}, // NOTE: passing empty struct to use default values
factory.ContractDeploymentData{
Contract: testdata.ERC20MinterV5Contract,
ConstructorArgs: []interface{}{
"Xmpl", "Xmpl",
},
},
)
Expect(err).ToNot(HaveOccurred(), "failed to deploy ERC20 minter contract")

erc20MinterV5CallerAddr, err := is.factory.DeployContract(
sender.Priv,
evmtypes.EvmTxArgs{}, // NOTE: passing empty struct to use default values
factory.ContractDeploymentData{
Contract: testdata.ERC20AllowanceCallerContract,
ConstructorArgs: []interface{}{
ERC20MinterV5Addr,
},
},
)
Expect(err).ToNot(HaveOccurred(), "failed to deploy ERC20 minter caller contract")

// Store the data of the deployed contracts
contractsData = ContractsData{
ownerPriv: sender.Priv,
contractData: map[CallType]ContractData{
directCall: {
Address: is.precompile.Address(),
ABI: is.precompile.ABI,
},
contractCall: {
Address: contractAddr,
ABI: testdata.ERC20AllowanceCallerContract.ABI,
},
erc20Call: {
Address: erc20MinterBurnerAddr,
ABI: contracts.ERC20MinterBurnerDecimalsContract.ABI,
},
erc20V5Call: {
Address: ERC20MinterV5Addr,
ABI: testdata.ERC20MinterV5Contract.ABI,
},
erc20V5CallerCall: {
Address: erc20MinterV5CallerAddr,
ABI: testdata.ERC20AllowanceCallerContract.ABI,
},
},
}

failCheck = testutil.LogCheckArgs{ABIEvents: is.precompile.Events}
execRevertedCheck = failCheck.WithErrContains("execution reverted")
passCheck = failCheck.WithExpPass(true)

err = is.network.NextBlock()
Expect(err).ToNot(HaveOccurred(), "failed to advance block")

// FIXME: remove once tests are added
_ = contractsData
_ = failCheck
_ = execRevertedCheck
_ = passCheck
})

Context("basic functionality -", func() {})

Context("metadata query -", func() {})

Context("allowance adjustments -", func() {})
})

var _ = Describe("ERC20 Extension migration Flows -", func() {
When("migrating an existing ERC20 token", func() {
var (
contractData ContractsData

tokenDenom = "xmpl"
tokenName = "Xmpl"
tokenSymbol = strings.ToUpper(tokenDenom)

supply = sdk.NewInt64Coin(tokenDenom, 1000000000000000000)
)

BeforeEach(func() {
is.SetupTest()

contractOwner := is.keyring.GetKey(0)

// Deploy an ERC20 contract
erc20Addr, err := is.factory.DeployContract(
contractOwner.Priv,
evmtypes.EvmTxArgs{}, // NOTE: passing empty struct to use default values
factory.ContractDeploymentData{
Contract: testdata.ERC20MinterV5Contract,
ConstructorArgs: []interface{}{
tokenName, tokenSymbol,
},
},
)
Expect(err).ToNot(HaveOccurred(), "failed to deploy contract")

// NOTE: We need to overwrite the information in the contractData here for this specific
// deployed contract.
contractData = ContractsData{
ownerPriv: contractOwner.Priv,
contractData: map[CallType]ContractData{
erc20V5Call: {
Address: erc20Addr,
ABI: testdata.ERC20MinterV5Contract.ABI,
},
},
}

err = is.network.NextBlock()
Expect(err).ToNot(HaveOccurred(), "failed to commit block")

// Register the deployed erc20 contract as a token pair
_, err = utils.RegisterERC20(is.factory, is.network, utils.ERC20RegistrationData{
Address: erc20Addr,
Denom: tokenDenom,
ProposerPriv: contractOwner.Priv,
})
Expect(err).ToNot(HaveOccurred(), "failed to register ERC20 token")

err = is.network.NextBlock()
Expect(err).ToNot(HaveOccurred(), "failed to commit block")

// Mint the supply of tokens
err = is.MintERC20(erc20V5Call, contractData, contractOwner.Addr, supply.Amount.BigInt())
Expect(err).ToNot(HaveOccurred(), "failed to mint tokens")

err = is.network.NextBlock()
Expect(err).ToNot(HaveOccurred(), "failed to commit block")

// Check that the supply was minted
is.ExpectBalancesForERC20(erc20V5Call, contractData, []ExpectedBalance{{
address: contractOwner.AccAddr,
expCoins: sdk.Coins{supply},
}})
})

It("should migrate the full token balance to the bank module", func() {
// TODO: implement test on follow-up PR
Skip("will be addressed on follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})
})

When("migrating an extended ERC20 token (e.g. ERC20Votes)", func() {
It("should migrate the full token balance to the bank module", func() {
// TODO: make sure that extended tokens are compatible with the ERC20 extensions
Skip("not included in first tranche")

Expect(true).To(BeFalse(), "not implemented")
})
})

When("running the migration logic for a set of existing ERC20 tokens", func() {
BeforeEach(func() {
// TODO: Add some ERC20 tokens and then run migration logic
// TODO: check here that the balance cannot be queried from the bank keeper before migrating the token
})

It("should add and enable the corresponding EVM extensions", func() {
Skip("will be addressed in follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})

It("should be possible to query the balances through the bank module", func() {
Skip("will be addressed in follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})

It("should return all tokens when querying all balances for an account", func() {
Skip("will be addressed in follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})
})

When("registering a native IBC coin", func() {
BeforeEach(func() {
// TODO: Add some IBC coins, register the token pair and then run migration logic
})

It("should add the corresponding EVM extensions", func() {
Skip("will be addressed in follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})

It("should be possible to query the balances using an EVM transaction", func() {
Skip("will be addressed in follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})
})

When("using Evmos (not wEvmos) in smart contracts", func() {
It("should be using straight Evmos for sending funds in smart contracts", func() {
Skip("will be addressed in follow-up PR")

Expect(true).To(BeFalse(), "not implemented")
})
})
})