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

     1  package keeper
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     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/29-fee/types"
    10  	channeltypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/04-channel/types"
    11  )
    12  
    13  // escrowPacketFee sends the packet fee to the 29-fee module account to hold in escrow
    14  func (k Keeper) escrowPacketFee(ctx sdk.Context, packetID channeltypes.PacketId, packetFee types.PacketFee) error {
    15  	// check if the refund address is valid
    16  	refundAddr, err := sdk.AccAddressFromBech32(packetFee.RefundAddress)
    17  	if err != nil {
    18  		return err
    19  	}
    20  
    21  	refundAcc := k.authKeeper.GetAccount(ctx, refundAddr)
    22  	if refundAcc == nil {
    23  		return sdkerrors.Wrapf(types.ErrRefundAccNotFound, "account with address: %s not found", packetFee.RefundAddress)
    24  	}
    25  
    26  	coins := packetFee.Fee.Total()
    27  
    28  	cm39Coins := coins.ToCoins()
    29  	if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, refundAddr, types.ModuleName, cm39Coins); err != nil {
    30  		return err
    31  	}
    32  
    33  	// multiple fees may be escrowed for a single packet, firstly create a slice containing the new fee
    34  	// retrieve any previous fees stored in escrow for the packet and append them to the list
    35  	fees := []types.PacketFee{packetFee}
    36  	if feesInEscrow, found := k.GetFeesInEscrow(ctx, packetID); found {
    37  		fees = append(fees, feesInEscrow.PacketFees...)
    38  	}
    39  
    40  	packetFees := types.NewPacketFees(fees)
    41  	k.SetFeesInEscrow(ctx, packetID, packetFees)
    42  
    43  	EmitIncentivizedPacketEvent(ctx, packetID, packetFees)
    44  
    45  	return nil
    46  }
    47  
    48  // DistributePacketFeesOnAcknowledgement pays all the acknowledgement & receive fees for a given packetID while refunding the timeout fees to the refund account.
    49  func (k Keeper) DistributePacketFeesOnAcknowledgement(ctx sdk.Context, forwardRelayer string, reverseRelayer sdk.AccAddress, packetFees []types.PacketFee, packetID channeltypes.PacketId) {
    50  	// cache context before trying to distribute fees
    51  	// if the escrow account has insufficient balance then we want to avoid partially distributing fees
    52  	cacheCtx, writeFn := ctx.CacheContext()
    53  
    54  	// forward relayer address will be empty if conversion fails
    55  	forwardAddr, _ := sdk.AccAddressFromBech32(forwardRelayer)
    56  
    57  	for _, packetFee := range packetFees {
    58  		if !k.EscrowAccountHasBalance(cacheCtx, packetFee.Fee.Total()) {
    59  			// if the escrow account does not have sufficient funds then there must exist a severe bug
    60  			// the fee module should be locked until manual intervention fixes the issue
    61  			// a locked fee module will simply skip fee logic, all channels will temporarily function as
    62  			// fee disabled channels
    63  			// NOTE: we use the uncached context to lock the fee module so that the state changes from
    64  			// locking the fee module are persisted
    65  			k.lockFeeModule(ctx)
    66  			return
    67  		}
    68  
    69  		// check if refundAcc address works
    70  		refundAddr, err := sdk.AccAddressFromBech32(packetFee.RefundAddress)
    71  		if err != nil {
    72  			panic(fmt.Sprintf("could not parse refundAcc %s to sdk.AccAddress", packetFee.RefundAddress))
    73  		}
    74  
    75  		k.distributePacketFeeOnAcknowledgement(cacheCtx, refundAddr, forwardAddr, reverseRelayer, packetFee)
    76  	}
    77  
    78  	// NOTE: The context returned by CacheContext() refers to a new EventManager, so it needs to explicitly set events to the original context.
    79  	ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events())
    80  
    81  	// write the cache
    82  	writeFn()
    83  
    84  	// removes the fees from the store as fees are now paid
    85  	k.DeleteFeesInEscrow(ctx, packetID)
    86  }
    87  
    88  // distributePacketFeeOnAcknowledgement pays the receive fee for a given packetID while refunding the timeout fee to the refund account associated with the Fee.
    89  // If there was no forward relayer or the associated forward relayer address is blocked, the receive fee is refunded.
    90  func (k Keeper) distributePacketFeeOnAcknowledgement(ctx sdk.Context, refundAddr, forwardRelayer, reverseRelayer sdk.AccAddress, packetFee types.PacketFee) {
    91  	// distribute fee to valid forward relayer address otherwise refund the fee
    92  	if !forwardRelayer.Empty() && !k.bankKeeper.BlockedAddr(forwardRelayer) {
    93  		// distribute fee for forward relaying
    94  		k.distributeFee(ctx, forwardRelayer, refundAddr, packetFee.Fee.RecvFee)
    95  	} else {
    96  		// refund onRecv fee as forward relayer is not valid address
    97  		k.distributeFee(ctx, refundAddr, refundAddr, packetFee.Fee.RecvFee)
    98  	}
    99  
   100  	// distribute fee for reverse relaying
   101  	k.distributeFee(ctx, reverseRelayer, refundAddr, packetFee.Fee.AckFee)
   102  
   103  	// refund timeout fee for unused timeout
   104  	k.distributeFee(ctx, refundAddr, refundAddr, packetFee.Fee.TimeoutFee)
   105  }
   106  
   107  // DistributePacketsFeesOnTimeout pays all the timeout fees for a given packetID while refunding the acknowledgement & receive fees to the refund account.
   108  func (k Keeper) DistributePacketFeesOnTimeout(ctx sdk.Context, timeoutRelayer sdk.AccAddress, packetFees []types.PacketFee, packetID channeltypes.PacketId) {
   109  	// cache context before trying to distribute fees
   110  	// if the escrow account has insufficient balance then we want to avoid partially distributing fees
   111  	cacheCtx, writeFn := ctx.CacheContext()
   112  
   113  	for _, packetFee := range packetFees {
   114  		if !k.EscrowAccountHasBalance(cacheCtx, packetFee.Fee.Total()) {
   115  			// if the escrow account does not have sufficient funds then there must exist a severe bug
   116  			// the fee module should be locked until manual intervention fixes the issue
   117  			// a locked fee module will simply skip fee logic, all channels will temporarily function as
   118  			// fee disabled channels
   119  			// NOTE: we use the uncached context to lock the fee module so that the state changes from
   120  			// locking the fee module are persisted
   121  			k.lockFeeModule(ctx)
   122  			return
   123  		}
   124  
   125  		// check if refundAcc address works
   126  		refundAddr, err := sdk.AccAddressFromBech32(packetFee.RefundAddress)
   127  		if err != nil {
   128  			panic(fmt.Sprintf("could not parse refundAcc %s to sdk.AccAddress", packetFee.RefundAddress))
   129  		}
   130  
   131  		k.distributePacketFeeOnTimeout(cacheCtx, refundAddr, timeoutRelayer, packetFee)
   132  	}
   133  
   134  	// NOTE: The context returned by CacheContext() refers to a new EventManager, so it needs to explicitly set events to the original context.
   135  	ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events())
   136  
   137  	// write the cache
   138  	writeFn()
   139  
   140  	// removing the fee from the store as the fee is now paid
   141  	k.DeleteFeesInEscrow(ctx, packetID)
   142  }
   143  
   144  // distributePacketFeeOnTimeout pays the timeout fee to the timeout relayer and refunds the acknowledgement & receive fee.
   145  func (k Keeper) distributePacketFeeOnTimeout(ctx sdk.Context, refundAddr, timeoutRelayer sdk.AccAddress, packetFee types.PacketFee) {
   146  	// refund receive fee for unused forward relaying
   147  	k.distributeFee(ctx, refundAddr, refundAddr, packetFee.Fee.RecvFee)
   148  
   149  	// refund ack fee for unused reverse relaying
   150  	k.distributeFee(ctx, refundAddr, refundAddr, packetFee.Fee.AckFee)
   151  
   152  	// distribute fee for timeout relaying
   153  	k.distributeFee(ctx, timeoutRelayer, refundAddr, packetFee.Fee.TimeoutFee)
   154  }
   155  
   156  // distributeFee will attempt to distribute the escrowed fee to the receiver address.
   157  // If the distribution fails for any reason (such as the receiving address being blocked),
   158  // the state changes will be discarded.
   159  func (k Keeper) distributeFee(ctx sdk.Context, receiver, refundAccAddress sdk.AccAddress, fee sdk.CoinAdapters) {
   160  	// cache context before trying to distribute fees
   161  	cacheCtx, writeFn := ctx.CacheContext()
   162  	sdkCoins := fee.ToCoins()
   163  	err := k.bankKeeper.SendCoinsFromModuleToAccount(cacheCtx, types.ModuleName, receiver, sdkCoins)
   164  	if err != nil {
   165  		if bytes.Equal(receiver, refundAccAddress) {
   166  			k.Logger(ctx).Error("error distributing fee", "receiver address", receiver, "fee", fee)
   167  			return // if sending to the refund address already failed, then return (no-op)
   168  		}
   169  
   170  		// if an error is returned from x/bank and the receiver is not the refundAccAddress
   171  		// then attempt to refund the fee to the original sender
   172  		err := k.bankKeeper.SendCoinsFromModuleToAccount(cacheCtx, types.ModuleName, refundAccAddress, sdkCoins)
   173  		if err != nil {
   174  			k.Logger(ctx).Error("error refunding fee to the original sender", "refund address", refundAccAddress, "fee", fee)
   175  			return // if sending to the refund address fails, no-op
   176  		}
   177  	}
   178  
   179  	// write the cache
   180  	writeFn()
   181  
   182  	// NOTE: The context returned by CacheContext() refers to a new EventManager, so it needs to explicitly set events to the original context.
   183  	ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events())
   184  }
   185  
   186  // RefundFeesOnChannelClosure will refund all fees associated with the given port and channel identifiers.
   187  // If the escrow account runs out of balance then fee module will become locked as this implies the presence
   188  // of a severe bug. When the fee module is locked, no fee distributions will be performed.
   189  // Please see ADR 004 for more information.
   190  func (k Keeper) RefundFeesOnChannelClosure(ctx sdk.Context, portID, channelID string) error {
   191  	identifiedPacketFees := k.GetIdentifiedPacketFeesForChannel(ctx, portID, channelID)
   192  
   193  	// cache context before trying to distribute fees
   194  	// if the escrow account has insufficient balance then we want to avoid partially distributing fees
   195  	cacheCtx, writeFn := ctx.CacheContext()
   196  
   197  	for _, identifiedPacketFee := range identifiedPacketFees {
   198  		var failedToSendCoins bool
   199  		for _, packetFee := range identifiedPacketFee.PacketFees {
   200  
   201  			if !k.EscrowAccountHasBalance(cacheCtx, packetFee.Fee.Total()) {
   202  				// if the escrow account does not have sufficient funds then there must exist a severe bug
   203  				// the fee module should be locked until manual intervention fixes the issue
   204  				// a locked fee module will simply skip fee logic, all channels will temporarily function as
   205  				// fee disabled channels
   206  				// NOTE: we use the uncached context to lock the fee module so that the state changes from
   207  				// locking the fee module are persisted
   208  				k.lockFeeModule(ctx)
   209  
   210  				// return a nil error so state changes are committed but distribution stops
   211  				return nil
   212  			}
   213  
   214  			refundAddr, err := sdk.AccAddressFromBech32(packetFee.RefundAddress)
   215  			if err != nil {
   216  				failedToSendCoins = true
   217  				continue
   218  			}
   219  
   220  			// refund all fees to refund address
   221  			sdkCoins := packetFee.Fee.Total().ToCoins()
   222  			if err = k.bankKeeper.SendCoinsFromModuleToAccount(cacheCtx, types.ModuleName, refundAddr, sdkCoins); err != nil {
   223  				failedToSendCoins = true
   224  				continue
   225  			}
   226  		}
   227  
   228  		if !failedToSendCoins {
   229  			k.DeleteFeesInEscrow(cacheCtx, identifiedPacketFee.PacketId)
   230  		}
   231  	}
   232  
   233  	// NOTE: The context returned by CacheContext() refers to a new EventManager, so it needs to explicitly set events to the original context.
   234  	ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events())
   235  
   236  	// write the cache
   237  	writeFn()
   238  
   239  	return nil
   240  }