github.com/Finschia/finschia-sdk@v0.48.1/x/bank/keeper/keeper.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 "github.com/Finschia/finschia-sdk/codec" 7 "github.com/Finschia/finschia-sdk/internal/conv" 8 "github.com/Finschia/finschia-sdk/store/prefix" 9 sdk "github.com/Finschia/finschia-sdk/types" 10 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 11 "github.com/Finschia/finschia-sdk/types/query" 12 authtypes "github.com/Finschia/finschia-sdk/x/auth/types" 13 vestexported "github.com/Finschia/finschia-sdk/x/auth/vesting/exported" 14 "github.com/Finschia/finschia-sdk/x/bank/types" 15 paramtypes "github.com/Finschia/finschia-sdk/x/params/types" 16 ) 17 18 var _ Keeper = (*BaseKeeper)(nil) 19 20 // Keeper defines a module interface that facilitates the transfer of coins 21 // between accounts. 22 type Keeper interface { 23 SendKeeper 24 25 InitGenesis(sdk.Context, *types.GenesisState) 26 ExportGenesis(sdk.Context) *types.GenesisState 27 28 GetSupply(ctx sdk.Context, denom string) sdk.Coin 29 HasSupply(ctx sdk.Context, denom string) bool 30 GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) 31 IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool) 32 33 GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) 34 SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) 35 IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool) 36 37 SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 38 SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error 39 SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 40 DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 41 UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 42 MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error 43 BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error 44 45 DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error 46 UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error 47 48 types.QueryServer 49 } 50 51 // BaseKeeper manages transfers between accounts. It implements the Keeper interface. 52 type BaseKeeper struct { 53 BaseSendKeeper 54 55 ak types.AccountKeeper 56 cdc codec.BinaryCodec 57 storeKey sdk.StoreKey 58 paramSpace paramtypes.Subspace 59 mintCoinsRestrictionFn MintingRestrictionFn 60 } 61 62 type MintingRestrictionFn func(ctx sdk.Context, coins sdk.Coins) error 63 64 // GetPaginatedTotalSupply queries for the supply, ignoring 0 coins, with a given pagination 65 func (k BaseKeeper) GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) { 66 store := ctx.KVStore(k.storeKey) 67 supplyStore := prefix.NewStore(store, types.SupplyKey) 68 69 supply := sdk.NewCoins() 70 71 pageRes, err := query.Paginate(supplyStore, pagination, func(key, value []byte) error { 72 var amount sdk.Int 73 err := amount.Unmarshal(value) 74 if err != nil { 75 return fmt.Errorf("unable to convert amount string to Int %v", err) 76 } 77 78 // `Add` omits the 0 coins addition to the `supply`. 79 supply = supply.Add(sdk.NewCoin(string(key), amount)) 80 return nil 81 }) 82 if err != nil { 83 return nil, nil, err 84 } 85 86 return supply, pageRes, nil 87 } 88 89 // NewBaseKeeper returns a new BaseKeeper object with a given codec, dedicated 90 // store key, an AccountKeeper implementation, and a parameter Subspace used to 91 // store and fetch module parameters. The BaseKeeper also accepts a 92 // blocklist map. This blocklist describes the set of addresses that are not allowed 93 // to receive funds through direct and explicit actions, for example, by using a MsgSend or 94 // by using a SendCoinsFromModuleToAccount execution. 95 func NewBaseKeeper( 96 cdc codec.BinaryCodec, 97 storeKey sdk.StoreKey, 98 ak types.AccountKeeper, 99 paramSpace paramtypes.Subspace, 100 blockedAddrs map[string]bool, 101 ) BaseKeeper { 102 // set KeyTable if it has not already been set 103 if !paramSpace.HasKeyTable() { 104 paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) 105 } 106 107 return BaseKeeper{ 108 BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, paramSpace, blockedAddrs), 109 ak: ak, 110 cdc: cdc, 111 storeKey: storeKey, 112 paramSpace: paramSpace, 113 mintCoinsRestrictionFn: func(ctx sdk.Context, coins sdk.Coins) error { return nil }, 114 } 115 } 116 117 // WithMintCoinsRestriction restricts the bank Keeper used within a specific module to 118 // have restricted permissions on minting via function passed in parameter. 119 // Previous restriction functions can be nested as such: 120 // 121 // bankKeeper.WithMintCoinsRestriction(restriction1).WithMintCoinsRestriction(restriction2) 122 func (k BaseKeeper) WithMintCoinsRestriction(check MintingRestrictionFn) BaseKeeper { 123 oldRestrictionFn := k.mintCoinsRestrictionFn 124 k.mintCoinsRestrictionFn = func(ctx sdk.Context, coins sdk.Coins) error { 125 err := check(ctx, coins) 126 if err != nil { 127 return err 128 } 129 err = oldRestrictionFn(ctx, coins) 130 if err != nil { 131 return err 132 } 133 return nil 134 } 135 return k 136 } 137 138 // DelegateCoins performs delegation by deducting amt coins from an account with 139 // address addr. For vesting accounts, delegations amounts are tracked for both 140 // vesting and vested coins. The coins are then transferred from the delegator 141 // address to a ModuleAccount address. If any of the delegation amounts are negative, 142 // an error is returned. 143 func (k BaseKeeper) DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error { 144 moduleAcc := k.ak.GetAccount(ctx, moduleAccAddr) 145 if moduleAcc == nil { 146 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr) 147 } 148 149 if !amt.IsValid() { 150 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 151 } 152 153 balances := sdk.NewCoins() 154 155 for _, coin := range amt { 156 balance := k.GetBalance(ctx, delegatorAddr, coin.GetDenom()) 157 if balance.IsLT(coin) { 158 return sdkerrors.Wrapf( 159 sdkerrors.ErrInsufficientFunds, "failed to delegate; %s is smaller than %s", balance, amt, 160 ) 161 } 162 163 balances = balances.Add(balance) 164 err := k.setBalance(ctx, delegatorAddr, balance.Sub(coin)) 165 if err != nil { 166 return err 167 } 168 } 169 170 if err := k.trackDelegation(ctx, delegatorAddr, balances, amt); err != nil { 171 return sdkerrors.Wrap(err, "failed to track delegation") 172 } 173 // emit coin spent event 174 ctx.EventManager().EmitEvent( 175 types.NewCoinSpentEvent(delegatorAddr, amt), 176 ) 177 178 err := k.addCoins(ctx, moduleAccAddr, amt) 179 if err != nil { 180 return err 181 } 182 183 return nil 184 } 185 186 // UndelegateCoins performs undelegation by crediting amt coins to an account with 187 // address addr. For vesting accounts, undelegation amounts are tracked for both 188 // vesting and vested coins. The coins are then transferred from a ModuleAccount 189 // address to the delegator address. If any of the undelegation amounts are 190 // negative, an error is returned. 191 func (k BaseKeeper) UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error { 192 moduleAcc := k.ak.GetAccount(ctx, moduleAccAddr) 193 if moduleAcc == nil { 194 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr) 195 } 196 197 if !amt.IsValid() { 198 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 199 } 200 201 err := k.subUnlockedCoins(ctx, moduleAccAddr, amt) 202 if err != nil { 203 return err 204 } 205 206 if err := k.trackUndelegation(ctx, delegatorAddr, amt); err != nil { 207 return sdkerrors.Wrap(err, "failed to track undelegation") 208 } 209 210 err = k.addCoins(ctx, delegatorAddr, amt) 211 if err != nil { 212 return err 213 } 214 215 return nil 216 } 217 218 // GetSupply retrieves the Supply from store 219 func (k BaseKeeper) GetSupply(ctx sdk.Context, denom string) sdk.Coin { 220 store := ctx.KVStore(k.storeKey) 221 supplyStore := prefix.NewStore(store, types.SupplyKey) 222 223 bz := supplyStore.Get([]byte(denom)) 224 if bz == nil { 225 return sdk.Coin{ 226 Denom: denom, 227 Amount: sdk.NewInt(0), 228 } 229 } 230 231 var amount sdk.Int 232 err := amount.Unmarshal(bz) 233 if err != nil { 234 panic(fmt.Errorf("unable to unmarshal supply value %v", err)) 235 } 236 237 return sdk.Coin{ 238 Denom: denom, 239 Amount: amount, 240 } 241 } 242 243 // HasSupply checks if the supply coin exists in store. 244 func (k BaseKeeper) HasSupply(ctx sdk.Context, denom string) bool { 245 store := ctx.KVStore(k.storeKey) 246 supplyStore := prefix.NewStore(store, types.SupplyKey) 247 return supplyStore.Has([]byte(denom)) 248 } 249 250 // GetDenomMetaData retrieves the denomination metadata. returns the metadata and true if the denom exists, 251 // false otherwise. 252 func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) { 253 store := ctx.KVStore(k.storeKey) 254 store = prefix.NewStore(store, types.DenomMetadataPrefix) 255 256 bz := store.Get([]byte(denom)) 257 if bz == nil { 258 return types.Metadata{}, false 259 } 260 261 var metadata types.Metadata 262 k.cdc.MustUnmarshal(bz, &metadata) 263 264 return metadata, true 265 } 266 267 // GetAllDenomMetaData retrieves all denominations metadata 268 func (k BaseKeeper) GetAllDenomMetaData(ctx sdk.Context) []types.Metadata { 269 denomMetaData := make([]types.Metadata, 0) 270 k.IterateAllDenomMetaData(ctx, func(metadata types.Metadata) bool { 271 denomMetaData = append(denomMetaData, metadata) 272 return false 273 }) 274 275 return denomMetaData 276 } 277 278 // IterateAllDenomMetaData iterates over all the denominations metadata and 279 // provides the metadata to a callback. If true is returned from the 280 // callback, iteration is halted. 281 func (k BaseKeeper) IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool) { 282 store := ctx.KVStore(k.storeKey) 283 denomMetaDataStore := prefix.NewStore(store, types.DenomMetadataPrefix) 284 285 iterator := denomMetaDataStore.Iterator(nil, nil) 286 defer iterator.Close() 287 288 for ; iterator.Valid(); iterator.Next() { 289 var metadata types.Metadata 290 k.cdc.MustUnmarshal(iterator.Value(), &metadata) 291 292 if cb(metadata) { 293 break 294 } 295 } 296 } 297 298 // SetDenomMetaData sets the denominations metadata 299 func (k BaseKeeper) SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) { 300 store := ctx.KVStore(k.storeKey) 301 denomMetaDataStore := prefix.NewStore(store, types.DenomMetadataPrefix) 302 303 m := k.cdc.MustMarshal(&denomMetaData) 304 denomMetaDataStore.Set([]byte(denomMetaData.Base), m) 305 } 306 307 // SendCoinsFromModuleToAccount transfers coins from a ModuleAccount to an AccAddress. 308 // It will panic if the module account does not exist. An error is returned if 309 // the recipient address is black-listed or if sending the tokens fails. 310 func (k BaseKeeper) SendCoinsFromModuleToAccount( 311 ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, 312 ) error { 313 senderAddr := k.ak.GetModuleAddress(senderModule) 314 if senderAddr.Empty() { 315 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule)) 316 } 317 318 if k.BlockedAddr(recipientAddr) { 319 return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", recipientAddr) 320 } 321 322 return k.SendCoins(ctx, senderAddr, recipientAddr, amt) 323 } 324 325 // SendCoinsFromModuleToModule transfers coins from a ModuleAccount to another. 326 // It will panic if either module account does not exist. 327 func (k BaseKeeper) SendCoinsFromModuleToModule( 328 ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins, 329 ) error { 330 senderAddr := k.ak.GetModuleAddress(senderModule) 331 if senderAddr.Empty() { 332 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule)) 333 } 334 335 recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule) 336 if recipientAcc == nil { 337 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule)) 338 } 339 340 return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt) 341 } 342 343 // SendCoinsFromAccountToModule transfers coins from an AccAddress to a ModuleAccount. 344 // It will panic if the module account does not exist. 345 func (k BaseKeeper) SendCoinsFromAccountToModule( 346 ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins, 347 ) error { 348 recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule) 349 if recipientAcc == nil { 350 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule)) 351 } 352 353 return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt) 354 } 355 356 // DelegateCoinsFromAccountToModule delegates coins and transfers them from a 357 // delegator account to a module account. It will panic if the module account 358 // does not exist or is unauthorized. 359 func (k BaseKeeper) DelegateCoinsFromAccountToModule( 360 ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins, 361 ) error { 362 recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule) 363 if recipientAcc == nil { 364 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule)) 365 } 366 367 if !recipientAcc.HasPermission(authtypes.Staking) { 368 panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to receive delegated coins", recipientModule)) 369 } 370 371 return k.DelegateCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt) 372 } 373 374 // UndelegateCoinsFromModuleToAccount undelegates the unbonding coins and transfers 375 // them from a module account to the delegator account. It will panic if the 376 // module account does not exist or is unauthorized. 377 func (k BaseKeeper) UndelegateCoinsFromModuleToAccount( 378 ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, 379 ) error { 380 acc := k.ak.GetModuleAccount(ctx, senderModule) 381 if acc == nil { 382 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule)) 383 } 384 385 if !acc.HasPermission(authtypes.Staking) { 386 panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to undelegate coins", senderModule)) 387 } 388 389 return k.UndelegateCoins(ctx, acc.GetAddress(), recipientAddr, amt) 390 } 391 392 // MintCoins creates new coins from thin air and adds it to the module account. 393 // It will panic if the module account does not exist or is unauthorized. 394 func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error { 395 err := k.mintCoinsRestrictionFn(ctx, amounts) 396 if err != nil { 397 ctx.Logger().Error(fmt.Sprintf("Module %q attempted to mint coins %s it doesn't have permission for, error %v", moduleName, amounts, err)) 398 return err 399 } 400 acc := k.ak.GetModuleAccount(ctx, moduleName) 401 if acc == nil { 402 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName)) 403 } 404 405 if !acc.HasPermission(authtypes.Minter) { 406 panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to mint tokens", moduleName)) 407 } 408 409 err = k.addCoins(ctx, acc.GetAddress(), amounts) 410 if err != nil { 411 return err 412 } 413 414 for _, amount := range amounts { 415 supply := k.GetSupply(ctx, amount.GetDenom()) 416 supply = supply.Add(amount) 417 k.setSupply(ctx, supply) 418 } 419 420 logger := k.Logger(ctx) 421 logger.Info("minted coins from module account", "amount", amounts.String(), "from", moduleName) 422 423 // emit mint event 424 ctx.EventManager().EmitEvent( 425 types.NewCoinMintEvent(acc.GetAddress(), amounts), 426 ) 427 428 return nil 429 } 430 431 // BurnCoins burns coins deletes coins from the balance of the module account. 432 // It will panic if the module account does not exist or is unauthorized. 433 func (k BaseKeeper) BurnCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error { 434 acc := k.ak.GetModuleAccount(ctx, moduleName) 435 if acc == nil { 436 panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName)) 437 } 438 439 if !acc.HasPermission(authtypes.Burner) { 440 panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to burn tokens", moduleName)) 441 } 442 443 err := k.subUnlockedCoins(ctx, acc.GetAddress(), amounts) 444 if err != nil { 445 return err 446 } 447 448 for _, amount := range amounts { 449 supply := k.GetSupply(ctx, amount.GetDenom()) 450 supply = supply.Sub(amount) 451 k.setSupply(ctx, supply) 452 } 453 454 logger := k.Logger(ctx) 455 logger.Info("burned tokens from module account", "amount", amounts.String(), "from", moduleName) 456 457 // emit burn event 458 ctx.EventManager().EmitEvent( 459 types.NewCoinBurnEvent(acc.GetAddress(), amounts), 460 ) 461 462 return nil 463 } 464 465 // setSupply sets the supply for the given coin 466 func (k BaseKeeper) setSupply(ctx sdk.Context, coin sdk.Coin) { 467 intBytes, err := coin.Amount.Marshal() 468 if err != nil { 469 panic(fmt.Errorf("unable to marshal amount value %v", err)) 470 } 471 472 store := ctx.KVStore(k.storeKey) 473 supplyStore := prefix.NewStore(store, types.SupplyKey) 474 475 // Bank invariants and IBC requires to remove zero coins. 476 if coin.IsZero() { 477 supplyStore.Delete(conv.UnsafeStrToBytes(coin.GetDenom())) 478 } else { 479 supplyStore.Set([]byte(coin.GetDenom()), intBytes) 480 } 481 } 482 483 // trackDelegation tracks the delegation of the given account if it is a vesting account 484 func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, balance, amt sdk.Coins) error { 485 acc := k.ak.GetAccount(ctx, addr) 486 if acc == nil { 487 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr) 488 } 489 490 vacc, ok := acc.(vestexported.VestingAccount) 491 if ok { 492 // TODO: return error on account.TrackDelegation 493 vacc.TrackDelegation(ctx.BlockHeader().Time, balance, amt) 494 k.ak.SetAccount(ctx, acc) 495 } 496 497 return nil 498 } 499 500 // trackUndelegation trakcs undelegation of the given account if it is a vesting account 501 func (k BaseKeeper) trackUndelegation(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error { 502 acc := k.ak.GetAccount(ctx, addr) 503 if acc == nil { 504 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr) 505 } 506 507 vacc, ok := acc.(vestexported.VestingAccount) 508 if ok { 509 // TODO: return error on account.TrackUndelegation 510 vacc.TrackUndelegation(amt) 511 k.ak.SetAccount(ctx, acc) 512 } 513 514 return nil 515 } 516 517 // IterateTotalSupply iterates over the total supply calling the given cb (callback) function 518 // with the balance of each coin. 519 // The iteration stops if the callback returns true. 520 func (k BaseViewKeeper) IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool) { 521 store := ctx.KVStore(k.storeKey) 522 supplyStore := prefix.NewStore(store, types.SupplyKey) 523 524 iterator := supplyStore.Iterator(nil, nil) 525 defer iterator.Close() 526 527 for ; iterator.Valid(); iterator.Next() { 528 var amount sdk.Int 529 err := amount.Unmarshal(iterator.Value()) 530 if err != nil { 531 panic(fmt.Errorf("unable to unmarshal supply value %v", err)) 532 } 533 534 balance := sdk.Coin{ 535 Denom: string(iterator.Key()), 536 Amount: amount, 537 } 538 539 if cb(balance) { 540 break 541 } 542 } 543 }