github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/ibc-go/modules/apps/transfer/keeper/relay.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 "strings" 6 7 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 8 sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 9 "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/apps/transfer/types" 10 clienttypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/02-client/types" 11 channeltypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/04-channel/types" 12 host "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/24-host" 13 ) 14 15 // SendTransfer handles transfer sending logic. There are 2 possible cases: 16 // 17 // 1. Sender chain is acting as the source zone. The coins are transferred 18 // to an escrow address (i.e locked) on the sender chain and then transferred 19 // to the receiving chain through IBC TAO logic. It is expected that the 20 // receiving chain will mint vouchers to the receiving address. 21 // 22 // 2. Sender chain is acting as the sink zone. The coins (vouchers) are burned 23 // on the sender chain and then transferred to the receiving chain though IBC 24 // TAO logic. It is expected that the receiving chain, which had previously 25 // sent the original denomination, will unescrow the fungible token and send 26 // it to the receiving address. 27 // 28 // Another way of thinking of source and sink zones is through the token's 29 // timeline. Each send to any chain other than the one it was previously 30 // received from is a movement forwards in the token's timeline. This causes 31 // trace to be added to the token's history and the destination port and 32 // destination channel to be prefixed to the denomination. In these instances 33 // the sender chain is acting as the source zone. When the token is sent back 34 // to the chain it previously received from, the prefix is removed. This is 35 // a backwards movement in the token's timeline and the sender chain 36 // is acting as the sink zone. 37 // 38 // Example: 39 // These steps of transfer occur: A -> B -> C -> A -> C -> B -> A 40 // 41 // 1. A -> B : sender chain is source zone. Denom upon receiving: 'B/denom' 42 // 2. B -> C : sender chain is source zone. Denom upon receiving: 'C/B/denom' 43 // 3. C -> A : sender chain is source zone. Denom upon receiving: 'A/C/B/denom' 44 // 4. A -> C : sender chain is sink zone. Denom upon receiving: 'C/B/denom' 45 // 5. C -> B : sender chain is sink zone. Denom upon receiving: 'B/denom' 46 // 6. B -> A : sender chain is sink zone. Denom upon receiving: 'denom' 47 func (k Keeper) SendTransfer( 48 ctx sdk.Context, 49 sourcePort, 50 sourceChannel string, 51 adapterToken sdk.CoinAdapter, 52 sender sdk.AccAddress, 53 receiver string, 54 timeoutHeight clienttypes.Height, 55 timeoutTimestamp uint64, 56 ) error { 57 if !k.GetSendEnabled(ctx) { 58 return types.ErrSendDisabled 59 } 60 61 transferAmountDec := sdk.NewDecFromIntWithPrec(adapterToken.Amount, sdk.Precision) 62 token := sdk.NewCoin(adapterToken.Denom, transferAmountDec) 63 k.Logger(ctx).Info("sendTransfer", 64 "token", token.String(), "tokenAmountBigInt", token.Amount.Int.String(), 65 "adapterToken", adapterToken.String(), "adapterTokenBigInt", adapterToken.Amount.String(), 66 ) 67 sourceChannelEnd, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) 68 if !found { 69 return sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) 70 } 71 72 destinationPort := sourceChannelEnd.GetCounterparty().GetPortID() 73 destinationChannel := sourceChannelEnd.GetCounterparty().GetChannelID() 74 75 // get the next sequence 76 sequence, found := k.channelKeeper.GetNextSequenceSend(ctx, sourcePort, sourceChannel) 77 if !found { 78 return sdkerrors.Wrapf( 79 channeltypes.ErrSequenceSendNotFound, 80 "source port: %s, source channel: %s", sourcePort, sourceChannel, 81 ) 82 } 83 84 // begin createOutgoingPacket logic 85 // See spec for this logic: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay 86 channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) 87 if !ok { 88 return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") 89 } 90 91 // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic 92 fullDenomPath := token.Denom 93 94 var err error 95 // deconstruct the token denomination into the denomination trace info 96 // to determine if the sender is the source chain 97 if strings.HasPrefix(token.Denom, "ibc/") { 98 fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) 99 if err != nil { 100 return err 101 } 102 } 103 104 // NOTE: SendTransfer simply sends the denomination as it exists on its own 105 // chain inside the packet data. The receiving chain will perform denom 106 // prefixing as necessary. 107 108 isSource := types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) 109 if isSource { 110 // create the escrow address for the tokens 111 escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) 112 113 // escrow source tokens. It fails if balance insufficient. 114 if token.Denom == sdk.DefaultIbcWei { 115 token.Denom = sdk.DefaultBondDenom 116 } 117 if err := k.bankKeeper.SendCoins( 118 ctx, sender, escrowAddress, sdk.NewCoins(token), 119 ); err != nil { 120 return err 121 } 122 } else { 123 // transfer the coins to the module account and burn them 124 if err := k.bankKeeper.SendCoinsFromAccountToModule( 125 ctx, sender, types.ModuleName, sdk.NewCoins(token), 126 ); err != nil { 127 return err 128 } 129 130 if err := k.bankKeeper.BurnCoins( 131 ctx, types.ModuleName, sdk.NewCoins(token), 132 ); err != nil { 133 // NOTE: should not happen as the module account was 134 // retrieved on the step above and it has enough balace 135 // to burn. 136 panic(fmt.Sprintf("cannot burn coins after a successful send to a module account: %v", err)) 137 } 138 } 139 packetData := types.NewFungibleTokenPacketData( 140 fullDenomPath, adapterToken.Amount.String(), sender.String(), receiver, 141 ) 142 143 packet := channeltypes.NewPacket( 144 packetData.GetBytes(), 145 sequence, 146 sourcePort, 147 sourceChannel, 148 destinationPort, 149 destinationChannel, 150 timeoutHeight, 151 timeoutTimestamp, 152 ) 153 154 if err := k.channelKeeper.SendPacket(ctx, channelCap, packet); err != nil { 155 return err 156 } 157 158 return k.CallAfterSendTransferHooks(ctx, sourcePort, sourceChannel, token, sender, receiver, isSource) 159 } 160 161 // OnRecvPacket processes a cross chain fungible token transfer. If the 162 // sender chain is the source of minted tokens then vouchers will be minted 163 // and sent to the receiving address. Otherwise if the sender chain is sending 164 // back tokens this chain originally transferred to it, the tokens are 165 // unescrowed and sent to the receiving address. 166 func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error { 167 // validate packet data upon receiving 168 if err := data.ValidateBasic(); err != nil { 169 return err 170 } 171 172 if !k.GetReceiveEnabled(ctx) { 173 return types.ErrReceiveDisabled 174 } 175 176 // decode the receiver address 177 receiver, err := sdk.AccAddressFromBech32(data.Receiver) 178 if err != nil { 179 return err 180 } 181 182 // parse the transfer amount 183 transferAmount, ok := sdk.NewIntFromString(data.Amount) 184 if !ok { 185 return sdkerrors.Wrapf(types.ErrInvalidAmount, "unable to parse transfer amount (%s) into sdk.Int", data.Amount) 186 } 187 transferAmountDec := sdk.NewDecFromIntWithPrec(transferAmount, sdk.Precision) 188 189 k.Logger(ctx).Info("OnRecvPacket", "transferAmount", transferAmount.String(), 190 "transferAmountDec", transferAmountDec.String(), "transferAmountDecInt", transferAmountDec.Int.String()) 191 192 // This is the prefix that would have been prefixed to the denomination 193 // on sender chain IF and only if the token originally came from the 194 // receiving chain. 195 // 196 // NOTE: We use SourcePort and SourceChannel here, because the counterparty 197 // chain would have prefixed with DestPort and DestChannel when originally 198 // receiving this coin as seen in the "sender chain is the source" condition. 199 200 isSource := types.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) 201 if isSource { 202 // sender chain is not the source, unescrow tokens 203 204 // remove prefix added by sender chain 205 voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) 206 unprefixedDenom := data.Denom[len(voucherPrefix):] 207 208 // coin denomination used in sending from the escrow address 209 denom := unprefixedDenom 210 211 // The denomination used to send the coins is either the native denom or the hash of the path 212 // if the denomination is not native. 213 denomTrace := types.ParseDenomTrace(unprefixedDenom) 214 if denomTrace.Path != "" { 215 denom = denomTrace.IBCDenom() 216 } 217 218 if denom == sdk.DefaultIbcWei { 219 denom = sdk.DefaultBondDenom 220 } 221 token := sdk.NewCoin(denom, transferAmountDec) 222 223 // unescrow tokens 224 escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) 225 ccc := sdk.NewCoins(token) 226 k.Logger(ctx).Info("new denomTrace", 227 "tracePath", denomTrace.Path, 228 "baseDenom", denomTrace.BaseDenom, 229 "token", token.String(), 230 "coins", ccc.String(), 231 ) 232 if err := k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, ccc); err != nil { 233 // NOTE: this error is only expected to occur given an unexpected bug or a malicious 234 // counterparty module. The bug may occur in bank or any part of the code that allows 235 // the escrow address to be drained. A malicious counterparty module could drain the 236 // escrow address by allowing more tokens to be sent back then were escrowed. 237 return sdkerrors.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") 238 } 239 240 return k.CallAfterRecvTransferHooks(ctx, packet.DestinationPort, packet.DestinationChannel, token, data.Receiver, isSource) 241 } 242 243 // sender chain is the source, mint vouchers 244 245 // since SendPacket did not prefix the denomination, we must prefix denomination here 246 sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) 247 // NOTE: sourcePrefix contains the trailing "/" 248 prefixedDenom := sourcePrefix + data.Denom 249 250 // construct the denomination trace from the full raw denomination 251 denomTrace := types.ParseDenomTrace(prefixedDenom) 252 traceHash := denomTrace.Hash() 253 if !k.HasDenomTrace(ctx, traceHash) { 254 k.SetDenomTrace(ctx, denomTrace) 255 } 256 257 voucherDenom := denomTrace.IBCDenom() 258 ctx.EventManager().EmitEvent( 259 sdk.NewEvent( 260 types.EventTypeDenomTrace, 261 sdk.NewAttribute(types.AttributeKeyTraceHash, traceHash.String()), 262 sdk.NewAttribute(types.AttributeKeyDenom, voucherDenom), 263 ), 264 ) 265 266 voucher := sdk.NewCoin(voucherDenom, transferAmountDec) 267 k.Logger(ctx).Info("on recvPacket", "info", voucher.String()) 268 269 // mint new tokens if the source of the transfer is the same chain 270 if err := k.bankKeeper.MintCoins( 271 ctx, types.ModuleName, sdk.NewIBCCoins(voucher), 272 ); err != nil { 273 return err 274 } 275 276 // send to receiver 277 ccc := sdk.NewCoins(voucher) 278 279 k.Logger(ctx).Info("new denomTrace", 280 "tracePath", denomTrace.Path, "baseDenom", 281 denomTrace.BaseDenom, "traceHash", traceHash, 282 "voucher", voucher.String(), 283 ) 284 285 if err := k.bankKeeper.SendCoinsFromModuleToAccount( 286 ctx, types.ModuleName, receiver, ccc, 287 ); err != nil { 288 panic(fmt.Sprintf("unable to send coins from module to account despite previously minting coins to module account: %v", err)) 289 } 290 291 return k.CallAfterRecvTransferHooks(ctx, packet.DestinationPort, packet.DestinationChannel, voucher, data.Receiver, isSource) 292 } 293 294 // OnAcknowledgementPacket responds to the the success or failure of a packet 295 // acknowledgement written on the receiving chain. If the acknowledgement 296 // was a success then nothing occurs. If the acknowledgement failed, then 297 // the sender is refunded their tokens using the refundPacketToken function. 298 func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData, ack channeltypes.Acknowledgement) error { 299 switch ack.Response.(type) { 300 case *channeltypes.Acknowledgement_Error: 301 return k.refundPacketToken(ctx, packet, data) 302 default: 303 // the acknowledgement succeeded on the receiving chain so nothing 304 // needs to be executed and no error needs to be returned 305 return nil 306 } 307 } 308 309 // OnTimeoutPacket refunds the sender since the original packet sent was 310 // never received and has been timed out. 311 func (k Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error { 312 return k.refundPacketToken(ctx, packet, data) 313 } 314 315 // refundPacketToken will unescrow and send back the tokens back to sender 316 // if the sending chain was the source chain. Otherwise, the sent tokens 317 // were burnt in the original send so new tokens are minted and sent to 318 // the sending address. 319 func (k Keeper) refundPacketToken(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error { 320 // NOTE: packet data type already checked in handler.go 321 322 // parse the denomination from the full denom path 323 trace := types.ParseDenomTrace(data.Denom) 324 325 // parse the transfer amount 326 transferAmount, ok := sdk.NewIntFromString(data.Amount) 327 if !ok { 328 return sdkerrors.Wrapf(types.ErrInvalidAmount, "unable to parse transfer amount (%s) into sdk.Int", data.Amount) 329 } 330 transferAmountDec := sdk.NewDecFromIntWithPrec(transferAmount, sdk.Precision) 331 token := sdk.NewCoin(trace.IBCDenom(), transferAmountDec) 332 333 // decode the sender address 334 sender, err := sdk.AccAddressFromBech32(data.Sender) 335 if err != nil { 336 return err 337 } 338 339 isSource := types.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) 340 if isSource { 341 // unescrow tokens back to sender 342 escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel()) 343 if token.Denom == sdk.DefaultIbcWei { 344 token.Denom = sdk.DefaultBondDenom 345 } 346 if err := k.bankKeeper.SendCoins(ctx, escrowAddress, sender, sdk.NewCoins(token)); err != nil { 347 // NOTE: this error is only expected to occur given an unexpected bug or a malicious 348 // counterparty module. The bug may occur in bank or any part of the code that allows 349 // the escrow address to be drained. A malicious counterparty module could drain the 350 // escrow address by allowing more tokens to be sent back then were escrowed. 351 return sdkerrors.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") 352 } 353 354 return k.CallAfterRefundTransferHooks(ctx, packet.SourcePort, packet.SourceChannel, token, data.Sender, isSource) 355 } 356 357 // mint vouchers back to sender 358 if err := k.bankKeeper.MintCoins( 359 ctx, types.ModuleName, sdk.NewCoins(token), 360 ); err != nil { 361 return err 362 } 363 364 if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, sdk.NewCoins(token)); err != nil { 365 panic(fmt.Sprintf("unable to send coins from module to account despite previously minting coins to module account: %v", err)) 366 } 367 368 return k.CallAfterRefundTransferHooks(ctx, packet.SourcePort, packet.SourceChannel, token, data.Sender, isSource) 369 } 370 371 // DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash 372 // component. 373 func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) { 374 // trim the denomination prefix, by default "ibc/" 375 hexHash := denom[len(types.DenomPrefix+"/"):] 376 377 hash, err := types.ParseHexHash(hexHash) 378 if err != nil { 379 return "", sdkerrors.Wrap(types.ErrInvalidDenomForTransfer, err.Error()) 380 } 381 382 denomTrace, found := k.GetDenomTrace(ctx, hash) 383 if !found { 384 return "", sdkerrors.Wrap(types.ErrTraceNotFound, hexHash) 385 } 386 387 fullDenomPath := denomTrace.GetFullDenomPath() 388 return fullDenomPath, nil 389 }