github.com/Finschia/finschia-sdk@v0.48.1/x/bank/keeper/send.go (about) 1 package keeper 2 3 import ( 4 "github.com/Finschia/finschia-sdk/codec" 5 "github.com/Finschia/finschia-sdk/telemetry" 6 sdk "github.com/Finschia/finschia-sdk/types" 7 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 8 "github.com/Finschia/finschia-sdk/x/bank/types" 9 paramtypes "github.com/Finschia/finschia-sdk/x/params/types" 10 ) 11 12 // SendKeeper defines a module interface that facilitates the transfer of coins 13 // between accounts without the possibility of creating coins. 14 type SendKeeper interface { 15 ViewKeeper 16 17 InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error 18 SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error 19 20 GetParams(ctx sdk.Context) types.Params 21 SetParams(ctx sdk.Context, params types.Params) 22 23 IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool 24 IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error 25 26 BlockedAddr(addr sdk.AccAddress) bool 27 } 28 29 var _ SendKeeper = (*BaseSendKeeper)(nil) 30 31 // BaseSendKeeper only allows transfers between accounts without the possibility of 32 // creating coins. It implements the SendKeeper interface. 33 type BaseSendKeeper struct { 34 BaseViewKeeper 35 36 cdc codec.BinaryCodec 37 ak types.AccountKeeper 38 storeKey sdk.StoreKey 39 paramSpace paramtypes.Subspace 40 41 // list of addresses that are restricted from receiving transactions 42 blockedAddrs map[string]bool 43 } 44 45 func NewBaseSendKeeper( 46 cdc codec.BinaryCodec, storeKey sdk.StoreKey, ak types.AccountKeeper, paramSpace paramtypes.Subspace, blockedAddrs map[string]bool, 47 ) BaseSendKeeper { 48 return BaseSendKeeper{ 49 BaseViewKeeper: NewBaseViewKeeper(cdc, storeKey, ak), 50 cdc: cdc, 51 ak: ak, 52 storeKey: storeKey, 53 paramSpace: paramSpace, 54 blockedAddrs: blockedAddrs, 55 } 56 } 57 58 // GetParams returns the total set of bank parameters. 59 func (k BaseSendKeeper) GetParams(ctx sdk.Context) (params types.Params) { 60 k.paramSpace.GetParamSet(ctx, ¶ms) 61 return params 62 } 63 64 // SetParams sets the total set of bank parameters. 65 func (k BaseSendKeeper) SetParams(ctx sdk.Context, params types.Params) { 66 k.paramSpace.SetParamSet(ctx, ¶ms) 67 } 68 69 // InputOutputCoins performs multi-send functionality. It accepts a series of 70 // inputs that correspond to a series of outputs. It returns an error if the 71 // inputs and outputs don't lineup or if any single transfer of tokens fails. 72 func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error { 73 // Safety check ensuring that when sending coins the keeper must maintain the 74 // Check supply invariant and validity of Coins. 75 if err := types.ValidateInputsOutputs(inputs, outputs); err != nil { 76 return err 77 } 78 79 for _, in := range inputs { 80 inAddress, err := sdk.AccAddressFromBech32(in.Address) 81 if err != nil { 82 return err 83 } 84 85 err = k.subUnlockedCoins(ctx, inAddress, in.Coins) 86 if err != nil { 87 return err 88 } 89 } 90 91 for _, out := range outputs { 92 outAddress, err := sdk.AccAddressFromBech32(out.Address) 93 if err != nil { 94 return err 95 } 96 err = k.addCoins(ctx, outAddress, out.Coins) 97 if err != nil { 98 return err 99 } 100 101 ctx.EventManager().EmitEvent( 102 sdk.NewEvent( 103 types.EventTypeTransfer, 104 sdk.NewAttribute(types.AttributeKeyRecipient, out.Address), 105 sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()), 106 ), 107 ) 108 109 // Create account if recipient does not exist. 110 // 111 // NOTE: This should ultimately be removed in favor a more flexible approach 112 // such as delegated fee messages. 113 accExists := k.ak.HasAccount(ctx, outAddress) 114 if !accExists { 115 defer telemetry.IncrCounter(1, "new", "account") 116 k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, outAddress)) 117 } 118 } 119 120 return nil 121 } 122 123 // SendCoins transfers amt coins from a sending account to a receiving account. 124 // An error is returned upon failure. 125 func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error { 126 err := k.subUnlockedCoins(ctx, fromAddr, amt) 127 if err != nil { 128 return err 129 } 130 131 err = k.addCoins(ctx, toAddr, amt) 132 if err != nil { 133 return err 134 } 135 136 // Create account if recipient does not exist. 137 // 138 // NOTE: This should ultimately be removed in favor a more flexible approach 139 // such as delegated fee messages. 140 accExists := k.ak.HasAccount(ctx, toAddr) 141 if !accExists { 142 defer telemetry.IncrCounter(1, "new", "account") 143 k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr)) 144 } 145 146 ctx.EventManager().EmitEvent( 147 sdk.NewEvent( 148 types.EventTypeTransfer, 149 sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()), 150 sdk.NewAttribute(types.AttributeKeySender, fromAddr.String()), 151 sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()), 152 ), 153 ) 154 155 return nil 156 } 157 158 // subUnlockedCoins removes the unlocked amt coins of the given account. An error is 159 // returned if the resulting balance is negative or the initial amount is invalid. 160 // A coin_spent event is emitted after. 161 func (k BaseSendKeeper) subUnlockedCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error { 162 if !amt.IsValid() { 163 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 164 } 165 166 lockedCoins := k.LockedCoins(ctx, addr) 167 168 for _, coin := range amt { 169 balance := k.GetBalance(ctx, addr, coin.Denom) 170 locked := sdk.NewCoin(coin.Denom, lockedCoins.AmountOf(coin.Denom)) 171 spendable := balance.Sub(locked) 172 173 _, hasNeg := sdk.Coins{spendable}.SafeSub(sdk.Coins{coin}) 174 if hasNeg { 175 return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "%s is smaller than %s", spendable, coin) 176 } 177 178 newBalance := balance.Sub(coin) 179 180 err := k.setBalance(ctx, addr, newBalance) 181 if err != nil { 182 return err 183 } 184 } 185 186 // emit coin spent event 187 ctx.EventManager().EmitEvent( 188 types.NewCoinSpentEvent(addr, amt), 189 ) 190 return nil 191 } 192 193 // addCoins increase the addr balance by the given amt. Fails if the provided amt is invalid. 194 // It emits a coin received event. 195 func (k BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error { 196 if !amt.IsValid() { 197 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 198 } 199 200 for _, coin := range amt { 201 balance := k.GetBalance(ctx, addr, coin.Denom) 202 newBalance := balance.Add(coin) 203 204 err := k.setBalance(ctx, addr, newBalance) 205 if err != nil { 206 return err 207 } 208 } 209 210 // emit coin received event 211 ctx.EventManager().EmitEvent( 212 types.NewCoinReceivedEvent(addr, amt), 213 ) 214 215 return nil 216 } 217 218 // initBalances sets the balance (multiple coins) for an account by address. 219 // An error is returned upon failure. 220 func (k BaseSendKeeper) initBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error { 221 accountStore := k.getAccountStore(ctx, addr) 222 for i := range balances { 223 balance := balances[i] 224 if !balance.IsValid() { 225 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String()) 226 } 227 228 // Bank invariants require to not store zero balances. 229 if !balance.IsZero() { 230 bz := k.cdc.MustMarshal(&balance) 231 accountStore.Set([]byte(balance.Denom), bz) 232 } 233 } 234 235 return nil 236 } 237 238 // setBalance sets the coin balance for an account by address. 239 func (k BaseSendKeeper) setBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error { 240 if !balance.IsValid() { 241 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String()) 242 } 243 244 accountStore := k.getAccountStore(ctx, addr) 245 246 // Bank invariants require to not store zero balances. 247 if balance.IsZero() { 248 accountStore.Delete([]byte(balance.Denom)) 249 } else { 250 bz := k.cdc.MustMarshal(&balance) 251 accountStore.Set([]byte(balance.Denom), bz) 252 } 253 254 return nil 255 } 256 257 // IsSendEnabledCoins checks the coins provide and returns an ErrSendDisabled if 258 // any of the coins are not configured for sending. Returns nil if sending is enabled 259 // for all provided coin 260 func (k BaseSendKeeper) IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error { 261 for _, coin := range coins { 262 if !k.IsSendEnabledCoin(ctx, coin) { 263 return sdkerrors.Wrapf(types.ErrSendDisabled, "%s transfers are currently disabled", coin.Denom) 264 } 265 } 266 return nil 267 } 268 269 // IsSendEnabledCoin returns the current SendEnabled status of the provided coin's denom 270 func (k BaseSendKeeper) IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool { 271 return k.GetParams(ctx).SendEnabledDenom(coin.Denom) 272 } 273 274 // BlockedAddr checks if a given address is restricted from 275 // receiving funds. 276 func (k BaseSendKeeper) BlockedAddr(addr sdk.AccAddress) bool { 277 return k.blockedAddrs[addr.String()] 278 }