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 }