github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/bank/internal/keeper/keeper.go (about) 1 package keeper 2 3 import ( 4 "bytes" 5 "fmt" 6 "time" 7 8 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec" 9 10 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log" 11 12 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 13 sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 14 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/innertx" 15 authexported "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/exported" 16 vestexported "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/vesting/exported" 17 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/bank/internal/types" 18 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/params" 19 "github.com/fibonacci-chain/fbc/libs/tendermint/global" 20 ) 21 22 var _ Keeper = (*BaseKeeper)(nil) 23 24 // Keeper defines a module interface that facilitates the transfer of coins 25 // between accounts. 26 type Keeper interface { 27 SendKeeper 28 29 DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error 30 UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error 31 32 GetInnerTxKeeper() innertx.InnerTxKeeper 33 } 34 35 // BaseKeeper manages transfers between accounts. It implements the Keeper interface. 36 type BaseKeeper struct { 37 BaseSendKeeper 38 39 ak types.AccountKeeper 40 paramSpace params.Subspace 41 42 marshal *codec.CodecProxy 43 } 44 45 // NewBaseKeeper returns a new BaseKeeper 46 func NewBaseKeeper( 47 ak types.AccountKeeper, paramSpace params.Subspace, blacklistedAddrs map[string]bool, 48 ) BaseKeeper { 49 50 ps := paramSpace.WithKeyTable(types.ParamKeyTable()) 51 return BaseKeeper{ 52 BaseSendKeeper: NewBaseSendKeeper(ak, ps, blacklistedAddrs), 53 ak: ak, 54 paramSpace: ps, 55 } 56 } 57 58 func NewBaseKeeperWithMarshal(ak types.AccountKeeper, marshal *codec.CodecProxy, paramSpace params.Subspace, blacklistedAddrs map[string]bool, 59 ) BaseKeeper { 60 ret := NewBaseKeeper(ak, paramSpace, blacklistedAddrs) 61 ret.marshal = marshal 62 return ret 63 } 64 65 // DelegateCoins performs delegation by deducting amt coins from an account with 66 // address addr. For vesting accounts, delegations amounts are tracked for both 67 // vesting and vested coins. 68 // The coins are then transferred from the delegator address to a ModuleAccount address. 69 // If any of the delegation amounts are negative, an error is returned. 70 func (keeper BaseKeeper) DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) (err error) { 71 defer func() { 72 if !ctx.IsCheckTx() && keeper.ik != nil { 73 keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, delegatorAddr, moduleAccAddr, innertx.CosmosCallType, innertx.DelegateCallName, amt, err) 74 } 75 }() 76 delegatorAcc := keeper.ak.GetAccount(ctx, delegatorAddr) 77 if delegatorAcc == nil { 78 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", delegatorAddr) 79 } 80 81 moduleAcc := keeper.ak.GetAccount(ctx, moduleAccAddr) 82 if moduleAcc == nil { 83 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr) 84 } 85 86 if !amt.IsValid() { 87 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 88 } 89 90 oldCoins := delegatorAcc.GetCoins() 91 92 _, hasNeg := oldCoins.SafeSub(amt) 93 if hasNeg { 94 return sdkerrors.Wrapf( 95 sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", oldCoins, amt, 96 ) 97 } 98 99 if err := trackDelegation(delegatorAcc, ctx.BlockHeader().Time, amt); err != nil { 100 return sdkerrors.Wrap(err, "failed to track delegation") 101 } 102 103 keeper.ak.SetAccount(ctx, delegatorAcc) 104 105 _, err = keeper.AddCoins(ctx, moduleAccAddr, amt) 106 if err != nil { 107 return err 108 } 109 110 return nil 111 } 112 113 // UndelegateCoins performs undelegation by crediting amt coins to an account with 114 // address addr. For vesting accounts, undelegation amounts are tracked for both 115 // vesting and vested coins. 116 // The coins are then transferred from a ModuleAccount address to the delegator address. 117 // If any of the undelegation amounts are negative, an error is returned. 118 func (keeper BaseKeeper) UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) (err error) { 119 defer func() { 120 if !ctx.IsCheckTx() && keeper.ik != nil { 121 keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, moduleAccAddr, delegatorAddr, innertx.CosmosCallType, innertx.UndelegateCallName, amt, err) 122 } 123 }() 124 125 delegatorAcc := keeper.ak.GetAccount(ctx, delegatorAddr) 126 if delegatorAcc == nil { 127 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", delegatorAddr) 128 } 129 130 moduleAcc := keeper.ak.GetAccount(ctx, moduleAccAddr) 131 if moduleAcc == nil { 132 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr) 133 } 134 135 if !amt.IsValid() { 136 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 137 } 138 139 oldCoins := moduleAcc.GetCoins() 140 141 newCoins, hasNeg := oldCoins.SafeSub(amt) 142 if hasNeg { 143 return sdkerrors.Wrapf( 144 sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", oldCoins, amt, 145 ) 146 } 147 148 if err = keeper.SetCoins(ctx, moduleAccAddr, newCoins); err != nil { 149 return err 150 } 151 152 if err := trackUndelegation(delegatorAcc, amt); err != nil { 153 return sdkerrors.Wrap(err, "failed to track undelegation") 154 } 155 156 keeper.ak.SetAccount(ctx, delegatorAcc) 157 return nil 158 } 159 160 // SendKeeper defines a module interface that facilitates the transfer of coins 161 // between accounts without the possibility of creating coins. 162 type SendKeeper interface { 163 ViewKeeper 164 165 InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error 166 SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error 167 168 SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) 169 AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) 170 SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error 171 172 GetSendEnabled(ctx sdk.Context) bool 173 SetSendEnabled(ctx sdk.Context, enabled bool) 174 175 BlacklistedAddr(addr sdk.AccAddress) bool 176 } 177 178 var _ SendKeeper = (*BaseSendKeeper)(nil) 179 180 // BaseSendKeeper only allows transfers between accounts without the possibility of 181 // creating coins. It implements the SendKeeper interface. 182 type BaseSendKeeper struct { 183 BaseViewKeeper 184 185 ak types.AccountKeeper 186 ask authexported.SizerAccountKeeper 187 paramSpace params.Subspace 188 189 // list of addresses that are restricted from receiving transactions 190 blacklistedAddrs map[string]bool 191 192 ik innertx.InnerTxKeeper 193 } 194 195 // NewBaseSendKeeper returns a new BaseSendKeeper. 196 func NewBaseSendKeeper( 197 ak types.AccountKeeper, paramSpace params.Subspace, blacklistedAddrs map[string]bool, 198 ) BaseSendKeeper { 199 200 bsk := BaseSendKeeper{ 201 BaseViewKeeper: NewBaseViewKeeper(ak), 202 ak: ak, 203 paramSpace: paramSpace, 204 blacklistedAddrs: blacklistedAddrs, 205 } 206 bsk.ask, _ = bsk.ak.(authexported.SizerAccountKeeper) 207 return bsk 208 } 209 210 // InputOutputCoins handles a list of inputs and outputs 211 func (keeper BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) (err error) { 212 defer func() { 213 if !ctx.IsCheckTx() && keeper.ik != nil { 214 for _, in := range inputs { 215 keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, in.Address, sdk.AccAddress{}, innertx.CosmosCallType, innertx.MultiCallName, in.Coins, err) 216 } 217 218 for _, out := range outputs { 219 keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, sdk.AccAddress{}, out.Address, innertx.CosmosCallType, innertx.MultiCallName, out.Coins, err) 220 } 221 } 222 }() 223 // Safety check ensuring that when sending coins the keeper must maintain the 224 // Check supply invariant and validity of Coins. 225 if err := types.ValidateInputsOutputs(inputs, outputs); err != nil { 226 return err 227 } 228 229 for _, in := range inputs { 230 _, err := keeper.SubtractCoins(ctx, in.Address, in.Coins) 231 if err != nil { 232 return err 233 } 234 235 ctx.EventManager().EmitEvent( 236 sdk.NewEvent( 237 sdk.EventTypeMessage, 238 sdk.NewAttribute(types.AttributeKeySender, in.Address.String()), 239 ), 240 ) 241 } 242 243 for _, out := range outputs { 244 _, err := keeper.AddCoins(ctx, out.Address, out.Coins) 245 if err != nil { 246 return err 247 } 248 249 ctx.EventManager().EmitEvent( 250 sdk.NewEvent( 251 types.EventTypeTransfer, 252 sdk.NewAttribute(types.AttributeKeyRecipient, out.Address.String()), 253 sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()), 254 ), 255 ) 256 257 // Create account if recipient does not exist. 258 // 259 // NOTE: This should ultimately be removed in favor a more flexible approach 260 // such as delegated fee messages. 261 acc := keeper.ak.GetAccount(ctx, out.Address) 262 if acc == nil { 263 keeper.ak.SetAccount(ctx, keeper.ak.NewAccountWithAddress(ctx, out.Address)) 264 } 265 } 266 267 return nil 268 } 269 270 // SendCoins moves coins from one account to another 271 func (keeper BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (err error) { 272 defer func() { 273 if !ctx.IsCheckTx() && keeper.ik != nil { 274 keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, fromAddr, toAddr, innertx.CosmosCallType, innertx.SendCallName, amt, err) 275 } 276 }() 277 fromAddrStr := fromAddr.String() 278 ctx.EventManager().EmitEvents(sdk.Events{ 279 // This event should have all info (to, from, amount) without looking at other events 280 sdk.NewEvent( 281 types.EventTypeTransfer, 282 sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()), 283 sdk.NewAttribute(types.AttributeKeySender, fromAddrStr), 284 sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()), 285 ), 286 sdk.NewEvent( 287 sdk.EventTypeMessage, 288 sdk.NewAttribute(types.AttributeKeySender, fromAddrStr), 289 ), 290 }) 291 292 if !amt.IsValid() { 293 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 294 } 295 296 fromAcc, _ := ctx.GetFromAccountCacheData().(authexported.Account) 297 toAcc, _ := ctx.GetToAccountCacheData().(authexported.Account) 298 fromAccGas, toAccGas := ctx.GetFromAccountCacheGas(), ctx.GetToAccountCacheGas() 299 300 fromAcc, fromAccGas = keeper.getAccount(&ctx, fromAddr, fromAcc, fromAccGas) 301 _, err = keeper.subtractCoins(ctx, fromAddr, fromAcc, fromAccGas, amt) 302 if err != nil { 303 return err 304 } 305 306 ctx.UpdateFromAccountCache(fromAcc, 0) 307 308 toAcc, toAccGas = keeper.getAccount(&ctx, toAddr, toAcc, toAccGas) 309 _, err = keeper.addCoins(ctx, toAddr, toAcc, toAccGas, amt) 310 if err != nil { 311 return err 312 } 313 314 ctx.UpdateToAccountCache(toAcc, 0) 315 316 return nil 317 } 318 319 // SubtractCoins subtracts amt from the coins at the addr. 320 // 321 // CONTRACT: If the account is a vesting account, the amount has to be spendable. 322 func (keeper BaseSendKeeper) SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) { 323 if !amt.IsValid() { 324 return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 325 } 326 acc, gasUsed := authexported.GetAccountAndGas(&ctx, keeper.ak, addr) 327 return keeper.subtractCoins(ctx, addr, acc, gasUsed, amt) 328 } 329 330 func (keeper *BaseSendKeeper) subtractCoins(ctx sdk.Context, addr sdk.AccAddress, acc authexported.Account, accGas sdk.Gas, amt sdk.Coins) (sdk.Coins, error) { 331 oldCoins, spendableCoins := sdk.NewCoins(), sdk.NewCoins() 332 if acc != nil { 333 oldCoins = acc.GetCoins() 334 spendableCoins = acc.SpendableCoins(ctx.BlockTime()) 335 } 336 337 // For non-vesting accounts, spendable coins will simply be the original coins. 338 // So the check here is sufficient instead of subtracting from oldCoins. 339 _, hasNeg := spendableCoins.SafeSub(amt) 340 if hasNeg { 341 return amt, sdkerrors.Wrapf( 342 sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", spendableCoins, amt, 343 ) 344 } 345 346 newCoins := oldCoins.Sub(amt) // should not panic as spendable coins was already checked 347 err := keeper.setCoinsToAccount(ctx, addr, acc, accGas, newCoins) 348 349 return newCoins, err 350 } 351 352 // AddCoins adds amt to the coins at the addr. 353 func (keeper BaseSendKeeper) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) { 354 if !amt.IsValid() { 355 return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 356 } 357 358 // oldCoins := keeper.GetCoins(ctx, addr) 359 360 acc, gasUsed := authexported.GetAccountAndGas(&ctx, keeper.ak, addr) 361 return keeper.addCoins(ctx, addr, acc, gasUsed, amt) 362 } 363 364 func (keeper *BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, acc authexported.Account, accGas sdk.Gas, amt sdk.Coins) (sdk.Coins, error) { 365 var oldCoins sdk.Coins 366 if acc == nil { 367 oldCoins = sdk.NewCoins() 368 } else { 369 oldCoins = acc.GetCoins() 370 } 371 372 newCoins := oldCoins.Add(amt...) 373 374 if newCoins.IsAnyNegative() { 375 return amt, sdkerrors.Wrapf( 376 sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", oldCoins, amt, 377 ) 378 } 379 380 err := keeper.setCoinsToAccount(ctx, addr, acc, accGas, newCoins) 381 382 return newCoins, err 383 } 384 385 // SetCoins sets the coins at the addr. 386 func (keeper BaseSendKeeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error { 387 if !amt.IsValid() { 388 sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 389 } 390 391 acc := keeper.ak.GetAccount(ctx, addr) 392 if acc == nil { 393 acc = keeper.ak.NewAccountWithAddress(ctx, addr) 394 } 395 396 err := acc.SetCoins(amt) 397 if err != nil { 398 panic(err) 399 } 400 401 keeper.ak.SetAccount(ctx, acc) 402 return nil 403 } 404 405 func (keeper *BaseSendKeeper) getAccount(ctx *sdk.Context, addr sdk.AccAddress, acc authexported.Account, getgas sdk.Gas) (authexported.Account, sdk.Gas) { 406 gasMeter := ctx.GasMeter() 407 if acc != nil && bytes.Equal(acc.GetAddress(), addr) { 408 if getgas > 0 { 409 gasMeter.ConsumeGas(getgas, "get account") 410 return acc, getgas 411 } 412 if ok, gasused := authexported.TryAddGetAccountGas(gasMeter, keeper.ask, acc); ok { 413 return acc, gasused 414 } 415 } 416 return authexported.GetAccountAndGas(ctx, keeper.ak, addr) 417 } 418 419 func (keeper *BaseSendKeeper) setCoinsToAccount(ctx sdk.Context, addr sdk.AccAddress, acc authexported.Account, accGas sdk.Gas, amt sdk.Coins) error { 420 if !amt.IsValid() { 421 return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String()) 422 } 423 424 acc, _ = keeper.getAccount(&ctx, addr, acc, accGas) 425 if acc == nil { 426 acc = keeper.ak.NewAccountWithAddress(ctx, addr) 427 } 428 429 err := acc.SetCoins(amt) 430 if err != nil { 431 panic(err) 432 } 433 434 keeper.ak.SetAccount(ctx, acc) 435 return nil 436 } 437 438 // GetSendEnabled returns the current SendEnabled 439 func (keeper BaseSendKeeper) GetSendEnabled(ctx sdk.Context) bool { 440 var enabled bool 441 keeper.paramSpace.Get(ctx, types.ParamStoreKeySendEnabled, &enabled) 442 return enabled 443 } 444 445 // SetSendEnabled sets the send enabled 446 func (keeper BaseSendKeeper) SetSendEnabled(ctx sdk.Context, enabled bool) { 447 global.Manager.SetSendEnabled(enabled) 448 keeper.paramSpace.Set(ctx, types.ParamStoreKeySendEnabled, &enabled) 449 } 450 451 // BlacklistedAddr checks if a given address is blacklisted (i.e restricted from 452 // receiving funds) 453 func (keeper BaseSendKeeper) BlacklistedAddr(addr sdk.AccAddress) bool { 454 return keeper.blacklistedAddrs[addr.String()] 455 } 456 457 // SetInnerTxKeeper set innerTxKeeper 458 func (k *BaseKeeper) SetInnerTxKeeper(keeper innertx.InnerTxKeeper) { 459 k.BaseSendKeeper.SetInnerTxKeeper(keeper) 460 } 461 462 func (k *BaseSendKeeper) SetInnerTxKeeper(keeper innertx.InnerTxKeeper) { 463 k.ik = keeper 464 } 465 466 func (k BaseSendKeeper) GetInnerTxKeeper() innertx.InnerTxKeeper { 467 return k.ik 468 } 469 470 var _ ViewKeeper = (*BaseViewKeeper)(nil) 471 472 // ViewKeeper defines a module interface that facilitates read only access to 473 // account balances. 474 type ViewKeeper interface { 475 GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 476 HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool 477 } 478 479 // BaseViewKeeper implements a read only keeper implementation of ViewKeeper. 480 type BaseViewKeeper struct { 481 ak types.AccountKeeper 482 } 483 484 // NewBaseViewKeeper returns a new BaseViewKeeper. 485 func NewBaseViewKeeper(ak types.AccountKeeper) BaseViewKeeper { 486 return BaseViewKeeper{ak: ak} 487 } 488 489 // Logger returns a module-specific logger. 490 func (keeper BaseViewKeeper) Logger(ctx sdk.Context) log.Logger { 491 return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) 492 } 493 494 // GetCoins returns the coins at the addr. 495 func (keeper BaseViewKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { 496 acc := keeper.ak.GetAccount(ctx, addr) 497 if acc == nil { 498 return sdk.NewCoins() 499 } 500 return acc.GetCoins() 501 } 502 503 // HasCoins returns whether or not an account has at least amt coins. 504 func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool { 505 return keeper.GetCoins(ctx, addr).IsAllGTE(amt) 506 } 507 508 // CONTRACT: assumes that amt is valid. 509 func trackDelegation(acc authexported.Account, blockTime time.Time, amt sdk.Coins) error { 510 vacc, ok := acc.(vestexported.VestingAccount) 511 if ok { 512 // TODO: return error on account.TrackDelegation 513 vacc.TrackDelegation(blockTime, amt) 514 } 515 516 return acc.SetCoins(acc.GetCoins().Sub(amt)) 517 } 518 519 // CONTRACT: assumes that amt is valid. 520 func trackUndelegation(acc authexported.Account, amt sdk.Coins) error { 521 vacc, ok := acc.(vestexported.VestingAccount) 522 if ok { 523 // TODO: return error on account.TrackUndelegation 524 vacc.TrackUndelegation(amt) 525 } 526 527 return acc.SetCoins(acc.GetCoins().Add(amt...)) 528 }