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 }