github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/ibc-go/modules/apps/transfer/keeper/relay_test.go (about)

     1  package keeper_test
     2  
     3  import (
     4  	"fmt"
     5  
     6  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
     7  	"github.com/fibonacci-chain/fbc/libs/ibc-go/testing/simapp"
     8  
     9  	// sdk "github.com/cosmos/cosmos-sdk/types"
    10  	"github.com/fibonacci-chain/fbc/libs/ibc-go/modules/apps/transfer/types"
    11  	clienttypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/02-client/types"
    12  	channeltypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/04-channel/types"
    13  	host "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/24-host"
    14  	ibctesting "github.com/fibonacci-chain/fbc/libs/ibc-go/testing"
    15  )
    16  
    17  // test sending from chainA to chainB using both coin that orignate on
    18  // chainA and coin that orignate on chainB
    19  func (suite *KeeperTestSuite) TestSendTransfer() {
    20  	var (
    21  		amount            sdk.Coin
    22  		path              *ibctesting.Path
    23  		err               error
    24  		transferAmountDec sdk.Dec
    25  	)
    26  
    27  	testCases := []struct {
    28  		msg            string
    29  		malleate       func()
    30  		sendFromSource bool
    31  		expPass        bool
    32  	}{
    33  		{"successful transfer from source chain",
    34  			func() {
    35  				suite.coordinator.CreateTransferChannels(path)
    36  				transferAmountDec = sdk.NewDecFromIntWithPrec(sdk.NewInt(100), 0)
    37  				amount = sdk.NewCoin(sdk.DefaultIbcWei, sdk.NewInt(100))
    38  			}, true, true},
    39  		{"successful transfer with coin from counterparty chain",
    40  			func() {
    41  				// send coin from chainA back to chainB
    42  				suite.coordinator.CreateTransferChannels(path)
    43  				amount = types.GetTransferCoin(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultIbcWei, 100)
    44  				transferAmountDec = sdk.NewDecFromIntWithPrec(sdk.NewInt(100), 0)
    45  			}, false, true},
    46  		{"source channel not found",
    47  			func() {
    48  				// channel references wrong ID
    49  				suite.coordinator.CreateTransferChannels(path)
    50  				path.EndpointA.ChannelID = ibctesting.InvalidID
    51  				amount = sdk.NewCoin(sdk.DefaultIbcWei, sdk.NewInt(100))
    52  				transferAmountDec = sdk.NewDecFromIntWithPrec(sdk.NewInt(100), 0)
    53  			}, true, false},
    54  		{"next seq send not found",
    55  			func() {
    56  				path.EndpointA.ChannelID = "channel-0"
    57  				path.EndpointB.ChannelID = "channel-0"
    58  				// manually create channel so next seq send is never set
    59  				suite.chainA.App().GetIBCKeeper().ChannelKeeper.SetChannel(
    60  					suite.chainA.GetContext(),
    61  					path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID,
    62  					channeltypes.NewChannel(channeltypes.OPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{path.EndpointA.ConnectionID}, ibctesting.DefaultChannelVersion),
    63  				)
    64  				suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
    65  				amount = sdk.NewCoin(sdk.DefaultIbcWei, sdk.NewInt(100))
    66  				transferAmountDec = sdk.NewDecFromIntWithPrec(sdk.NewInt(100), 0)
    67  			}, true, false},
    68  
    69  		// createOutgoingPacket tests
    70  		// - source chain
    71  		// okc demom not validate will panic will return error
    72  		//{"send coin failed",
    73  		//	func() {
    74  		//		suite.coordinator.CreateTransferChannels(path)
    75  		//		//amount = sdk.NewCoin("randomdenom", sdk.NewInt(100))
    76  		//		amount = sdk.NewCoinAdapter("randomdenom", sdk.NewIntFromBigInt(big.NewInt(100)))
    77  		//	}, true, false},
    78  		// - receiving chain
    79  		{"send from module account failed",
    80  			func() {
    81  				suite.coordinator.CreateTransferChannels(path)
    82  				amount = types.GetTransferCoin(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, " randomdenom", 100)
    83  			}, false, false},
    84  		{"channel capability not found",
    85  			func() {
    86  				suite.coordinator.CreateTransferChannels(path)
    87  				cap := suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
    88  
    89  				// Release channel capability
    90  				suite.chainA.GetSimApp().ScopedTransferKeeper.ReleaseCapability(suite.chainA.GetContext(), cap)
    91  				amount = sdk.NewCoin(sdk.DefaultIbcWei, sdk.NewInt(100))
    92  			}, true, false},
    93  	}
    94  
    95  	for _, tc := range testCases {
    96  		tc := tc
    97  
    98  		suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
    99  			suite.SetupTest() // reset
   100  			path = NewTransferPath(suite.chainA, suite.chainB)
   101  			suite.coordinator.SetupConnections(path)
   102  
   103  			tc.malleate()
   104  
   105  			if !tc.sendFromSource {
   106  				// send coin from chainB to chainA
   107  				coinFromBToA := sdk.NewCoin(sdk.DefaultIbcWei, sdk.NewInt(100))
   108  				transferMsg := types.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, coinFromBToA, suite.chainB.SenderAccount().GetAddress(), suite.chainA.SenderAccount().GetAddress().String(), clienttypes.NewHeight(0, 110), 0)
   109  				_, err = suite.chainB.SendMsgs(transferMsg)
   110  				suite.Require().NoError(err) // message committed
   111  
   112  				// receive coin on chainA from chainB
   113  				fungibleTokenPacket := types.NewFungibleTokenPacketData(coinFromBToA.Denom, transferAmountDec.BigInt().String(), suite.chainB.SenderAccount().GetAddress().String(), suite.chainA.SenderAccount().GetAddress().String())
   114  				packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, clienttypes.NewHeight(0, 110), 0)
   115  
   116  				// get proof of packet commitment from chainB
   117  				err = path.EndpointA.UpdateClient()
   118  				suite.Require().NoError(err)
   119  				packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
   120  				proof, proofHeight := path.EndpointB.QueryProof(packetKey)
   121  
   122  				recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, suite.chainA.SenderAccount().GetAddress().String())
   123  				_, err = suite.chainA.SendMsgs(recvMsg)
   124  				suite.Require().NoError(err) // message committed
   125  			}
   126  
   127  			err = suite.chainA.GetSimApp().TransferKeeper.SendTransfer(
   128  				suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.NewCoinAdapter(amount.Denom, sdk.NewIntFromBigInt(amount.Amount.BigInt())),
   129  				suite.chainA.SenderAccount().GetAddress(), suite.chainB.SenderAccount().GetAddress().String(), clienttypes.NewHeight(0, 110), 0,
   130  			)
   131  
   132  			if tc.expPass {
   133  				suite.Require().NoError(err)
   134  			} else {
   135  				suite.Require().Error(err)
   136  			}
   137  		})
   138  	}
   139  }
   140  
   141  // test receiving coin on chainB with coin that orignate on chainA and
   142  // coin that orignated on chainB (source). The bulk of the testing occurs
   143  // in the test case for loop since setup is intensive for all cases. The
   144  // malleate function allows for testing invalid cases.
   145  func (suite *KeeperTestSuite) TestOnRecvPacket() {
   146  	var (
   147  		trace             types.DenomTrace
   148  		amount            sdk.Int
   149  		transferAmountDec sdk.Dec
   150  		receiver          string
   151  	)
   152  
   153  	testCases := []struct {
   154  		msg          string
   155  		malleate     func()
   156  		recvIsSource bool // the receiving chain is the source of the coin originally
   157  		expPass      bool
   158  	}{
   159  		{"success receive on source chain", func() {}, true, true},
   160  		{"success receive with coin from another chain as source", func() {}, false, true},
   161  		{"empty coin", func() {
   162  			trace = types.DenomTrace{}
   163  			amount = sdk.ZeroInt()
   164  			transferAmountDec = sdk.NewDecFromIntWithPrec(amount, 0)
   165  
   166  		}, true, false},
   167  		{"invalid receiver address", func() {
   168  			receiver = "gaia1scqhwpgsmr6vmztaa7suurfl52my6nd2kmrudl"
   169  		}, true, false},
   170  
   171  		// onRecvPacket
   172  		// - coin from chain chainA
   173  		{"failure: mint zero coin", func() {
   174  			amount = sdk.ZeroInt()
   175  			transferAmountDec = sdk.NewDecFromIntWithPrec(amount, 0)
   176  		}, false, false},
   177  
   178  		// - coin being sent back to original chain (chainB)
   179  		{"tries to unescrow more tokens than allowed", func() {
   180  			amount = sdk.NewInt(1000000)
   181  			transferAmountDec = sdk.NewDecFromIntWithPrec(amount, 0)
   182  		}, true, false},
   183  	}
   184  
   185  	for _, tc := range testCases {
   186  		tc := tc
   187  
   188  		suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
   189  			suite.SetupTest() // reset
   190  
   191  			path := NewTransferPath(suite.chainA, suite.chainB)
   192  			suite.coordinator.Setup(path)
   193  			receiver = suite.chainB.SenderAccount().GetAddress().String() // must be explicitly changed in malleate
   194  
   195  			amount = sdk.NewInt(100) // must be explicitly changed in malleate
   196  			transferAmountDec = sdk.NewDecFromIntWithPrec(amount, 0)
   197  			seq := uint64(1)
   198  
   199  			if tc.recvIsSource {
   200  				// send coin from chainB to chainA, receive them, acknowledge them, and send back to chainB
   201  				coinFromBToA := sdk.NewCoin(sdk.DefaultIbcWei, sdk.NewInt(100))
   202  				transferMsg := types.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, coinFromBToA, suite.chainB.SenderAccount().GetAddress(), suite.chainA.SenderAccount().GetAddress().String(), clienttypes.NewHeight(0, 110), 0)
   203  				_, err := suite.chainB.SendMsgs(transferMsg)
   204  				suite.Require().NoError(err) // message committed
   205  
   206  				// relay send packet
   207  				fungibleTokenPacket := types.NewFungibleTokenPacketData(coinFromBToA.Denom, transferAmountDec.BigInt().String(), suite.chainB.SenderAccount().GetAddress().String(), suite.chainA.SenderAccount().GetAddress().String())
   208  				packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, clienttypes.NewHeight(0, 110), 0)
   209  				ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
   210  				err = path.RelayPacket(packet, ack.Acknowledgement())
   211  				suite.Require().NoError(err) // relay committed
   212  
   213  				seq++
   214  
   215  				// NOTE: trace must be explicitly changed in malleate to test invalid cases
   216  				trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultIbcWei))
   217  			} else {
   218  				trace = types.ParseDenomTrace(sdk.DefaultIbcWei)
   219  			}
   220  
   221  			// send coin from chainA to chainB
   222  			transferMsg := types.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.NewCoin(trace.IBCDenom(), amount), suite.chainA.SenderAccount().GetAddress(), receiver, clienttypes.NewHeight(0, 110), 0)
   223  			_, err := suite.chainA.SendMsgs(transferMsg)
   224  			suite.Require().NoError(err) // message committed
   225  
   226  			tc.malleate()
   227  
   228  			data := types.NewFungibleTokenPacketData(trace.GetFullDenomPath(), transferAmountDec.BigInt().String(), suite.chainA.SenderAccount().GetAddress().String(), receiver)
   229  			packet := channeltypes.NewPacket(data.GetBytes(), seq, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0)
   230  
   231  			err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainB.GetContext(), packet, data)
   232  
   233  			if tc.expPass {
   234  				suite.Require().NoError(err)
   235  			} else {
   236  				suite.Require().Error(err)
   237  			}
   238  		})
   239  	}
   240  }
   241  
   242  // TestOnAcknowledgementPacket tests that successful acknowledgement is a no-op
   243  // and failure acknowledment leads to refund when attempting to send from chainA
   244  // to chainB. If sender is source than the denomination being refunded has no
   245  // trace.
   246  func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() {
   247  	var (
   248  		successAck = channeltypes.NewResultAcknowledgement([]byte{byte(1)})
   249  		failedAck  = channeltypes.NewErrorAcknowledgement("failed packet transfer")
   250  		trace      types.DenomTrace
   251  		amount     sdk.Int
   252  		path       *ibctesting.Path
   253  	)
   254  
   255  	testCases := []struct {
   256  		msg      string
   257  		ack      channeltypes.Acknowledgement
   258  		malleate func()
   259  		success  bool // success of ack
   260  		expPass  bool
   261  	}{
   262  		{"success ack causes no-op", successAck, func() {
   263  			trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sdk.DefaultIbcWei))
   264  		}, true, true},
   265  		{"successful refund from source chain", failedAck, func() {
   266  			escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
   267  			trace = types.ParseDenomTrace(sdk.DefaultBondDenom)
   268  			coin := sdk.NewCoin(sdk.DefaultBondDenom, amount)
   269  
   270  			suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin)))
   271  		}, false, true},
   272  		{"unsuccessful refund from source", failedAck,
   273  			func() {
   274  				trace = types.ParseDenomTrace(sdk.DefaultIbcWei)
   275  			}, false, false},
   276  		{"successful refund from with coin from external chain", failedAck,
   277  			func() {
   278  				escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
   279  				trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultIbcWei))
   280  				coin := sdk.NewCoin(trace.IBCDenom(), amount)
   281  
   282  				suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin)))
   283  			}, false, true},
   284  	}
   285  
   286  	for _, tc := range testCases {
   287  		tc := tc
   288  
   289  		suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
   290  			suite.SetupTest() // reset
   291  			path = NewTransferPath(suite.chainA, suite.chainB)
   292  			suite.coordinator.Setup(path)
   293  			amount = sdk.NewInt(100) // must be explicitly changed
   294  
   295  			tc.malleate()
   296  
   297  			data := types.NewFungibleTokenPacketData(trace.GetFullDenomPath(), amount.String(), suite.chainA.SenderAccount().GetAddress().String(), suite.chainB.SenderAccount().GetAddress().String())
   298  			packet := channeltypes.NewPacket(data.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0)
   299  
   300  			// preCoin := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), trace.IBCDenom())
   301  			ctx := suite.chainA.GetContext()
   302  			preCoin := suite.chainA.GetSimApp().BankKeeper.GetCoins(ctx, suite.chainA.SenderAccount().GetAddress())
   303  			err := suite.chainA.GetSimApp().TransferKeeper.OnAcknowledgementPacket(ctx, packet, data, tc.ack)
   304  			if tc.expPass {
   305  				suite.Require().NoError(err)
   306  				// postCoin := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), trace.IBCDenom())
   307  				postCoin := suite.chainA.GetSimApp().BankKeeper.GetCoins(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress())
   308  				// deltaAmount := postCoin.Amount.Sub(preCoin.Amount)
   309  				deltaAmount := postCoin.Sub(preCoin)
   310  
   311  				if tc.success {
   312  					// suite.Require().Equal(int64(0), deltaAmount.Int64(), "successful ack changed balance")
   313  					suite.Require().Equal(int64(0), deltaAmount.AmountOf(trace.IBCDenom()).Int64(), "successful ack changed balance")
   314  				} else {
   315  					//suite.Require().Equal(amount, deltaAmount, "failed ack did not trigger refund")
   316  					suite.Require().Equal(amount.Int64(), deltaAmount.AmountOf(trace.IBCDenom()).Int64(), "failed ack did not trigger refund")
   317  				}
   318  
   319  			} else {
   320  				suite.Require().Error(err)
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  // TestOnTimeoutPacket test private refundPacket function since it is a simple
   327  // wrapper over it. The actual timeout does not matter since IBC core logic
   328  // is not being tested. The test is timing out a send from chainA to chainB
   329  // so the refunds are occurring on chainA.
   330  func (suite *KeeperTestSuite) TestOnTimeoutPacket() {
   331  	var (
   332  		trace  types.DenomTrace
   333  		path   *ibctesting.Path
   334  		amount sdk.Int
   335  		sender string
   336  	)
   337  
   338  	testCases := []struct {
   339  		msg      string
   340  		malleate func()
   341  		expPass  bool
   342  	}{
   343  		{"successful timeout from sender as source chain",
   344  			func() {
   345  				escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
   346  				trace = types.ParseDenomTrace(sdk.DefaultBondDenom)
   347  				coin := sdk.NewCoin(trace.IBCDenom(), amount)
   348  
   349  				suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin)))
   350  			}, true},
   351  		{"successful timeout from external chain",
   352  			func() {
   353  				escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
   354  				trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultIbcWei))
   355  				coin := sdk.NewCoin(trace.IBCDenom(), amount)
   356  
   357  				suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin)))
   358  			}, true},
   359  		{"no balance for coin denom",
   360  			func() {
   361  				trace = types.ParseDenomTrace("bitcoin")
   362  			}, false},
   363  		{"unescrow failed",
   364  			func() {
   365  				trace = types.ParseDenomTrace(sdk.DefaultIbcWei)
   366  			}, false},
   367  		{"mint failed",
   368  			func() {
   369  				trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultIbcWei))
   370  				amount = sdk.OneInt()
   371  				sender = "invalid address"
   372  			}, false},
   373  	}
   374  
   375  	for _, tc := range testCases {
   376  		tc := tc
   377  
   378  		suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
   379  			//suite.SetupTest() // reset
   380  
   381  			path = NewTransferPath(suite.chainA, suite.chainB)
   382  			suite.coordinator.Setup(path)
   383  			amount = sdk.NewInt(100) // must be explicitly changed
   384  			sender = suite.chainA.SenderAccount().GetAddress().String()
   385  
   386  			tc.malleate()
   387  
   388  			data := types.NewFungibleTokenPacketData(trace.GetFullDenomPath(), amount.String(), sender, suite.chainB.SenderAccount().GetAddress().String())
   389  			packet := channeltypes.NewPacket(data.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0)
   390  
   391  			// preCoin := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), trace.IBCDenom())
   392  			preCoin := suite.chainA.GetSimApp().BankKeeper.GetCoins(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress())
   393  
   394  			err := suite.chainA.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet, data)
   395  
   396  			// postCoin := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), trace.IBCDenom())
   397  			postCoin := suite.chainA.GetSimApp().BankKeeper.GetCoins(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress())
   398  			// deltaAmount := postCoin.Amount.Sub(preCoin.Amount)
   399  			deltaAmount := postCoin.Sub(preCoin)
   400  
   401  			if tc.expPass {
   402  				suite.Require().NoError(err)
   403  				// suite.Require().Equal(amount.Int64(), deltaAmount.Int64(), "successful timeout did not trigger refund")
   404  				suite.Require().Equal(amount.Int64(), deltaAmount.AmountOf(trace.IBCDenom()).Int64(), "successful timeout did not trigger refund")
   405  			} else {
   406  				suite.Require().Error(err)
   407  			}
   408  		})
   409  	}
   410  }