From 26962a888097365d65b12a6724a92687cdd14b2f Mon Sep 17 00:00:00 2001 From: suha jin <89185836+djm07073@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:15:59 +0900 Subject: [PATCH] test:wrapper --- integration-tests/erc721_transfer_test.go | 105 +--------- integration-tests/testsuite_test.go | 122 ++++++++++++ integration-tests/wrapper_test.go | 226 ++++++++++++++++++++++ 3 files changed, 352 insertions(+), 101 deletions(-) create mode 100644 integration-tests/testsuite_test.go create mode 100644 integration-tests/wrapper_test.go diff --git a/integration-tests/erc721_transfer_test.go b/integration-tests/erc721_transfer_test.go index 1cec86af..f07124fb 100644 --- a/integration-tests/erc721_transfer_test.go +++ b/integration-tests/erc721_transfer_test.go @@ -2,108 +2,23 @@ package test import ( "math/big" - "testing" - "time" - - "github.com/stretchr/testify/suite" - - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - nfttransferkeeper "github.com/initia-labs/initia/x/ibc/nft-transfer/keeper" nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" ibctesting "github.com/initia-labs/initia/x/ibc/testing" - minievmapp "github.com/initia-labs/minievm/app" evmkeeper "github.com/initia-labs/minievm/x/evm/keeper" evmtypes "github.com/initia-labs/minievm/x/evm/types" "github.com/ethereum/go-ethereum/common" ) -type KeeperTestSuite struct { - suite.Suite - - coordinator *ibctesting.Coordinator - - // testing chains used for convenience and readability - // MinievmAppChain is the chain used by the testing suite - chainA *ibctesting.TestChain - // MinievmAppChain is the chain used by the testing suite - chainB *ibctesting.TestChain - // InitiaAppChain is the chain used by the testing suite - chainC *ibctesting.TestChain - - queryClient nfttransfertypes.QueryClient -} - -func getMinitiaApp(chain *ibctesting.TestChain) *minievmapp.MinitiaApp { - return chain.App.(*minievmapp.MinitiaApp) -} - -const ( - TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 / 3 - UnbondingPeriod time.Duration = time.Hour * 24 * 7 -) - -func (suite *KeeperTestSuite) convertAppToMApp(chain *ibctesting.TestChain) { - genAccs := make([]authtypes.GenesisAccount, len(chain.SenderAccounts)) - genBals := make([]banktypes.Balance, len(chain.SenderAccounts)) - for i, acc := range chain.SenderAccounts { - genAccs[i] = acc.SenderAccount.(*authtypes.BaseAccount) - amount, ok := math.NewIntFromString("10000000000000000000") - suite.Require().True(ok) - - // add sender account - balance := banktypes.Balance{ - Address: genAccs[i].GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), - } - genBals[i] = balance - } - - miniApp := minievmapp.SetupWithGenesisAccounts(chain.Vals.Copy(), genAccs, genBals...) - baseapp.SetChainID(chain.ChainID)(miniApp.GetBaseApp()) - chain.App = miniApp - chain.Codec = miniApp.AppCodec() - chain.TxConfig = miniApp.TxConfig() - - chain.CurrentHeader = cmtproto.Header{ - ChainID: chain.ChainID, - Height: chain.App.LastBlockHeight() + 1, - AppHash: chain.App.LastCommitID().Hash, - Time: chain.CurrentHeader.Time, - ValidatorsHash: chain.Vals.Hash(), - NextValidatorsHash: chain.NextVals.Hash(), - ProposerAddress: chain.CurrentHeader.ProposerAddress, - } -} - -func (suite *KeeperTestSuite) SetupTest() { - suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) - suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) - - suite.convertAppToMApp(suite.chainA) - suite.convertAppToMApp(suite.chainB) - suite.convertAppToMApp(suite.chainC) - - queryHelper := baseapp.NewQueryServerTestHelper(suite.chainA.GetContext(), getMinitiaApp(suite.chainA).InterfaceRegistry()) - nfttransfertypes.RegisterQueryServer(queryHelper, nfttransferkeeper.NewQueryServerImpl(getMinitiaApp(suite.chainA).NftTransferKeeper)) - suite.queryClient = nfttransfertypes.NewQueryClient(queryHelper) -} - -func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { +func NewNftTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { path := ibctesting.NewPath(chainA, chainB) path.EndpointA.ChannelConfig.PortID = nfttransfertypes.PortID path.EndpointB.ChannelConfig.PortID = nfttransfertypes.PortID @@ -116,10 +31,6 @@ func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { return path } -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) -} - func (suite *KeeperTestSuite) CreateNftClass( endpoint *ibctesting.Endpoint, name, uri string, @@ -214,7 +125,7 @@ func (suite *KeeperTestSuite) TestSendAndReceive() { // pathB2C.EndpointA is ChainB endpoint (source of path)` // pathB2C.EndpointB is ChainC endpoint (destination of path) // pathA2B.EndpointB.Chain.SenderAccount is same with receiver account of pathA2B before testing` - pathA2B := NewTransferPath(suite.chainA, suite.chainB) + pathA2B := NewNftTransferPath(suite.chainA, suite.chainB) suite.Run("transfer forward A->B", func() { { suite.coordinator.SetupConnections(pathA2B) @@ -247,7 +158,7 @@ func (suite *KeeperTestSuite) TestSendAndReceive() { }) // transfer from chainB to chainC - pathB2C := NewTransferPath(suite.chainB, suite.chainC) + pathB2C := NewNftTransferPath(suite.chainB, suite.chainC) suite.Run("transfer forward B->C", func() { { suite.coordinator.SetupConnections(pathB2C) @@ -368,17 +279,9 @@ func (suite *KeeperTestSuite) receiverNft( suite.Require().NoError(err) // get proof of packet commitment from chainA - err = toEndpoint.UpdateClient() + err = commitRecvPacket(fromEndpoint, toEndpoint, packet) suite.Require().NoError(err) - packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - proof, proofHeight := fromEndpoint.QueryProof(packetKey) - - recvMsg := channeltypes.NewMsgRecvPacket( - packet, proof, proofHeight, toEndpoint.Chain.SenderAccount.GetAddress().String()) - _, err = toEndpoint.Chain.SendMsgs(recvMsg) - suite.Require().NoError(err) // message committed - var classId string isAwayFromOrigin := nfttransfertypes.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.GetClassId()) diff --git a/integration-tests/testsuite_test.go b/integration-tests/testsuite_test.go new file mode 100644 index 00000000..a832ad6d --- /dev/null +++ b/integration-tests/testsuite_test.go @@ -0,0 +1,122 @@ +package test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + + nfttransferkeeper "github.com/initia-labs/initia/x/ibc/nft-transfer/keeper" + nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" + ibctesting "github.com/initia-labs/initia/x/ibc/testing" + + minievmapp "github.com/initia-labs/minievm/app" +) + +type KeeperTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + // MinievmAppChain is the chain used by the testing suite + chainA *ibctesting.TestChain + // MinievmAppChain is the chain used by the testing suite + chainB *ibctesting.TestChain + // InitiaAppChain is the chain used by the testing suite + chainC *ibctesting.TestChain + + queryClient nfttransfertypes.QueryClient +} + +func getMinitiaApp(chain *ibctesting.TestChain) *minievmapp.MinitiaApp { + return chain.App.(*minievmapp.MinitiaApp) +} + +const ( + TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 / 3 + UnbondingPeriod time.Duration = time.Hour * 24 * 7 +) + +func (suite *KeeperTestSuite) convertAppToMApp(chain *ibctesting.TestChain) { + genAccs := make([]authtypes.GenesisAccount, len(chain.SenderAccounts)) + genBals := make([]banktypes.Balance, len(chain.SenderAccounts)) + for i, acc := range chain.SenderAccounts { + genAccs[i] = acc.SenderAccount.(*authtypes.BaseAccount) + amount, ok := math.NewIntFromString("10000000000000000000") + suite.Require().True(ok) + + // add sender account + balance := banktypes.Balance{ + Address: genAccs[i].GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), + } + genBals[i] = balance + } + + miniApp := minievmapp.SetupWithGenesisAccounts(chain.Vals.Copy(), genAccs, genBals...) + baseapp.SetChainID(chain.ChainID)(miniApp.GetBaseApp()) + chain.App = miniApp + chain.Codec = miniApp.AppCodec() + chain.TxConfig = miniApp.TxConfig() + + chain.CurrentHeader = cmtproto.Header{ + ChainID: chain.ChainID, + Height: chain.App.LastBlockHeight() + 1, + AppHash: chain.App.LastCommitID().Hash, + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.Vals.Hash(), + NextValidatorsHash: chain.NextVals.Hash(), + ProposerAddress: chain.CurrentHeader.ProposerAddress, + } +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) + + suite.convertAppToMApp(suite.chainA) + suite.convertAppToMApp(suite.chainB) + suite.convertAppToMApp(suite.chainC) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.chainA.GetContext(), getMinitiaApp(suite.chainA).InterfaceRegistry()) + nfttransfertypes.RegisterQueryServer(queryHelper, nfttransferkeeper.NewQueryServerImpl(getMinitiaApp(suite.chainA).NftTransferKeeper)) + suite.queryClient = nfttransfertypes.NewQueryClient(queryHelper) +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func commitRecvPacket(fromEndpoint *ibctesting.Endpoint, toEndpoint *ibctesting.Endpoint, packet channeltypes.Packet) error { + err := toEndpoint.UpdateClient() + if err != nil { + return err + } + + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := fromEndpoint.QueryProof(packetKey) + + recvMsg := channeltypes.NewMsgRecvPacket( + packet, proof, proofHeight, toEndpoint.Chain.SenderAccount.GetAddress().String()) + _, err = toEndpoint.Chain.SendMsgs(recvMsg) + if err != nil { + return err + } + + return nil +} diff --git a/integration-tests/wrapper_test.go b/integration-tests/wrapper_test.go new file mode 100644 index 00000000..679a5400 --- /dev/null +++ b/integration-tests/wrapper_test.go @@ -0,0 +1,226 @@ +package test + +import ( + "encoding/hex" + "encoding/json" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + ibctesting "github.com/initia-labs/initia/x/ibc/testing" + "github.com/initia-labs/minievm/x/evm/types" +) + +func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.Version = transfertypes.Version + path.EndpointB.ChannelConfig.Version = transfertypes.Version + path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = TrustingPeriod + path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = UnbondingPeriod + path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod = TrustingPeriod + path.EndpointB.ClientConfig.(*ibctesting.TendermintConfig).UnbondingPeriod = UnbondingPeriod + + return path +} + +func (suite *KeeperTestSuite) TestE2ETokenWrapper() { + suite.SetupTest() + pathA2B := NewTransferPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(pathA2B) + suite.coordinator.CreateTransferChannels(pathA2B) + + bankKeeperA := getMinitiaApp(suite.chainA).BankKeeper + bankKeeperB := getMinitiaApp(suite.chainB).BankKeeper + + amount, _ := new(big.Int).SetString("10000000000000000000", 10) + userA := pathA2B.EndpointA.Chain.SenderAccount.GetAddress() + userB := pathA2B.EndpointB.Chain.SenderAccount.GetAddress() + tokenA := suite.createAndMintERC20(pathA2B.EndpointA, userA, amount) + balancesA := bankKeeperA.GetAllBalances(pathA2B.EndpointA.Chain.GetContext(), userA) + balancesB := bankKeeperB.GetAllBalances(pathA2B.EndpointB.Chain.GetContext(), userB) + // wrap + // tokenB: wrapped and ibc-transfered tokenA + tokenB, wrapperAddr := suite.wrap(pathA2B, tokenA, userA, userB, amount, big.NewInt(suite.chainB.CurrentHeader.Time.UnixNano()+1000000000000000000)) + // unwrap + suite.unwrap(pathA2B, tokenA, tokenB, wrapperAddr, userB, userA) + + // balance check + suite.Require().Equal(balancesB, bankKeeperB.GetAllBalances(pathA2B.EndpointB.Chain.GetContext(), userB)) + suite.Require().Equal(balancesA, bankKeeperA.GetAllBalances(pathA2B.EndpointA.Chain.GetContext(), userA)) +} + +func (suite *KeeperTestSuite) createAndMintERC20(endpoint *ibctesting.Endpoint, to sdk.AccAddress, amount *big.Int) common.Address { + ctx := endpoint.Chain.GetContext() + toAddr := common.BytesToAddress(to) + evmKeeper := getMinitiaApp(endpoint.Chain).EVMKeeper + erc20Keeper := evmKeeper.ERC20Keeper() + + ethFactoryAddr, err := evmKeeper.GetERC20FactoryAddr(ctx) + suite.Require().NoError(err) + + abi := erc20Keeper.GetERC20FactoryABI() + + // Create + inputBz, err := abi.Pack("createERC20", "foo", "foo", uint8(18)) + suite.Require().NoError(err) + + result, _, err := evmKeeper.EVMCall(ctx, toAddr, ethFactoryAddr, inputBz, nil, nil) + suite.Require().NoError(err) + tokenAddr := common.BytesToAddress(result) + + // Mint + abi = evmKeeper.ERC20Keeper().GetERC20ABI() + suite.Require().NoError(err) + + inputBz, err = abi.Pack("mint", toAddr, amount) + suite.Require().NoError(err) + + _, _, err = evmKeeper.EVMCall(ctx, toAddr, tokenAddr, inputBz, nil, nil) + suite.Require().NoError(err) + + return tokenAddr +} + +// wrap the tokens and transfer token from A to B +func (suite *KeeperTestSuite) wrap( + path *ibctesting.Path, + tokenAddress common.Address, + sender sdk.AccAddress, + receiver sdk.AccAddress, + amount *big.Int, + timeout *big.Int, +) (sdk.Coin, common.Address) { + fromEndpoint := path.EndpointA + toEndpoint := path.EndpointB + fromCtx := fromEndpoint.Chain.GetContext() + toCtx := toEndpoint.Chain.GetContext() + + senderAddr := common.BytesToAddress(sender) + bankKeeper := getMinitiaApp(toEndpoint.Chain).BankKeeper + coins := bankKeeper.GetAllBalances(toCtx, receiver) + suite.Require().Equal(1, coins.Len()) + + evmKeeper := getMinitiaApp(fromEndpoint.Chain).EVMKeeper + erc20Keeper := evmKeeper.ERC20Keeper() + + wrapperAddr, err := evmKeeper.GetERC20WrapperAddr(fromCtx) + suite.Require().NoError(err) + // approve + inputBz, err := erc20Keeper.GetERC20ABI().Pack("approve", wrapperAddr, amount) + suite.Require().NoError(err) + _, _, err = evmKeeper.EVMCall(fromCtx, senderAddr, tokenAddress, inputBz, nil, nil) + suite.Require().NoError(err) + // wrap + inputBz, err = erc20Keeper.GetERC20WrapperABI().Pack("wrap", fromEndpoint.ChannelID, tokenAddress, receiver.String(), amount, timeout) + suite.Require().NoError(err) + + senderStr, err := suite.chainA.Codec.InterfaceRegistry().SigningContext().AddressCodec().BytesToString(sender) + suite.Require().NoError(err) + msgWrap := &types.MsgCall{ + Sender: senderStr, + ContractAddr: wrapperAddr.Hex(), + Input: "0x" + common.Bytes2Hex(inputBz), + } + res, err := fromEndpoint.Chain.SendMsgs(msgWrap) + suite.Require().NoError(err) + packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + + var data transfertypes.FungibleTokenPacketData + err = suite.chainA.Codec.UnmarshalJSON(packet.GetData(), &data) + suite.Require().NoError(err) + + err = commitRecvPacket(fromEndpoint, toEndpoint, packet) + suite.Require().NoError(err) + + // check balance of recevier after wrap and send tokens + coins = bankKeeper.GetAllBalances(toCtx, receiver) + suite.Require().Equal(2, coins.Len()) + return coins[0], wrapperAddr + +} + +// Transfer token from B to A and unwrap the tokens +func (suite *KeeperTestSuite) unwrap( + path *ibctesting.Path, + tokenA common.Address, + tokenB sdk.Coin, + wrapperAddr common.Address, + sender sdk.AccAddress, + recevier sdk.AccAddress, +) { + fromEndpoint := path.EndpointB + toEndpoint := path.EndpointA + + evmKeeper := getMinitiaApp(fromEndpoint.Chain).EVMKeeper + erc20Keeper := evmKeeper.ERC20Keeper() + + // set hook message + inputBz, err := erc20Keeper.GetERC20WrapperABI().Pack("unwrap", tokenA, common.BytesToAddress(recevier)) + suite.Require().NoError(err) + hook, err := unwrapHook(UnwrapHookData{ + EVM: struct { + Message struct { + ContractAddr string `json:"contract_addr"` + Input string `json:"input"` + Value string `json:"value"` + AccessList []string `json:"access_list"` + } `json:"message"` + }{ + Message: struct { + ContractAddr string `json:"contract_addr"` + Input string `json:"input"` + Value string `json:"value"` + AccessList []string `json:"access_list"` + }{ + ContractAddr: wrapperAddr.String(), + Input: "0x" + hex.EncodeToString(inputBz), + Value: "0", + AccessList: nil, + }, + }, + }) + suite.Require().NoError(err) + + msgTransfer := transfertypes.NewMsgTransfer( + fromEndpoint.ChannelConfig.PortID, + fromEndpoint.ChannelID, + tokenB, + sender.String(), + wrapperAddr.Hex(), + toEndpoint.Chain.GetTimeoutHeight(), + uint64(toEndpoint.Chain.CurrentHeader.Time.UnixNano()+1000000000000000000), + hook, + ) + + res, err := fromEndpoint.Chain.SendMsgs(msgTransfer) + suite.Require().NoError(err) + + packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + + var data transfertypes.FungibleTokenPacketData + err = suite.chainA.Codec.UnmarshalJSON(packet.GetData(), &data) + suite.Require().NoError(err) + + err = commitRecvPacket(fromEndpoint, toEndpoint, packet) + suite.Require().NoError(err) +} + +type UnwrapHookData struct { + EVM struct { + Message struct { + ContractAddr string `json:"contract_addr"` + Input string `json:"input"` + Value string `json:"value"` + AccessList []string `json:"access_list"` + } `json:"message"` + } `json:"evm"` +} + +func unwrapHook(data UnwrapHookData) (string, error) { + bz, err := json.Marshal(data) + + return string(bz), err +}