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  }