359 lines
10 KiB
Go
359 lines
10 KiB
Go
package keeper
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
"github.com/cerc-io/laconicd/x/bond/types"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
auth "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
|
bank "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
|
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
|
)
|
|
|
|
// prefixIDToBondIndex is the prefix for ID -> Bond index in the KVStore.
|
|
// Note: This is the primary index in the system.
|
|
// Note: Golang doesn't support const arrays.
|
|
var prefixIDToBondIndex = []byte{0x00}
|
|
|
|
// prefixOwnerToBondsIndex is the prefix for the Owner -> [Bond] index in the KVStore.
|
|
var prefixOwnerToBondsIndex = []byte{0x01}
|
|
|
|
// Keeper maintains the link to storage and exposes getter/setter methods for the various parts of the state machine
|
|
type Keeper struct {
|
|
accountKeeper auth.AccountKeeper
|
|
bankKeeper bank.Keeper
|
|
|
|
// Track bond usage in other cosmos-sdk modules (more like a usage tracker).
|
|
usageKeepers []types.BondUsageKeeper
|
|
|
|
storeKey storetypes.StoreKey
|
|
|
|
cdc codec.BinaryCodec
|
|
|
|
paramSubspace paramtypes.Subspace
|
|
}
|
|
|
|
// NewKeeper creates new instances of the bond Keeper
|
|
func NewKeeper(cdc codec.BinaryCodec,
|
|
accountKeeper auth.AccountKeeper,
|
|
bankKeeper bank.Keeper,
|
|
usageKeepers []types.BondUsageKeeper,
|
|
storeKey storetypes.StoreKey,
|
|
ps paramtypes.Subspace,
|
|
) Keeper {
|
|
// set KeyTable if it has not already been set
|
|
if !ps.HasKeyTable() {
|
|
ps = ps.WithKeyTable(types.ParamKeyTable())
|
|
}
|
|
|
|
return Keeper{
|
|
accountKeeper: accountKeeper,
|
|
bankKeeper: bankKeeper,
|
|
storeKey: storeKey,
|
|
cdc: cdc,
|
|
usageKeepers: usageKeepers,
|
|
paramSubspace: ps,
|
|
}
|
|
}
|
|
|
|
// Generates Bond ID -> Bond index key.
|
|
func getBondIndexKey(id string) []byte {
|
|
return append(prefixIDToBondIndex, []byte(id)...)
|
|
}
|
|
|
|
// Generates Owner -> Bonds index key.
|
|
func getOwnerToBondsIndexKey(owner string, bondID string) []byte {
|
|
return append(append(prefixOwnerToBondsIndex, []byte(owner)...), []byte(bondID)...)
|
|
}
|
|
|
|
// BondID simplifies generation of bond IDs.
|
|
type BondID struct {
|
|
Address sdk.Address
|
|
AccNum uint64
|
|
Sequence uint64
|
|
}
|
|
|
|
// Generate creates the bond ID.
|
|
func (bondID BondID) Generate() string {
|
|
hasher := sha256.New()
|
|
str := fmt.Sprintf("%s:%d:%d", bondID.Address.String(), bondID.AccNum, bondID.Sequence)
|
|
hasher.Write([]byte(str))
|
|
return hex.EncodeToString(hasher.Sum(nil))
|
|
}
|
|
|
|
// MatchBonds - get all matching bonds.
|
|
func (k Keeper) MatchBonds(ctx sdk.Context, matchFn func(*types.Bond) bool) []*types.Bond {
|
|
var bonds []*types.Bond
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
itr := sdk.KVStorePrefixIterator(store, prefixIDToBondIndex)
|
|
defer itr.Close()
|
|
for ; itr.Valid(); itr.Next() {
|
|
bz := store.Get(itr.Key())
|
|
if bz != nil {
|
|
var obj types.Bond
|
|
k.cdc.MustUnmarshal(bz, &obj)
|
|
if matchFn(&obj) {
|
|
bonds = append(bonds, &obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
return bonds
|
|
}
|
|
|
|
// CreateBond creates a new bond.
|
|
func (k Keeper) CreateBond(ctx sdk.Context, ownerAddress sdk.AccAddress, coins sdk.Coins) (*types.Bond, error) {
|
|
// Check if account has funds.
|
|
for _, coin := range coins {
|
|
balance := k.bankKeeper.HasBalance(ctx, ownerAddress, coin)
|
|
if !balance {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInsufficientFunds, "failed to create bond; Insufficient funds")
|
|
}
|
|
}
|
|
|
|
// Generate bond ID.
|
|
account := k.accountKeeper.GetAccount(ctx, ownerAddress)
|
|
bondID := BondID{
|
|
Address: ownerAddress,
|
|
AccNum: account.GetAccountNumber(),
|
|
Sequence: account.GetSequence(),
|
|
}.Generate()
|
|
|
|
maxBondAmount := k.getMaxBondAmount(ctx)
|
|
|
|
bond := types.Bond{Id: bondID, Owner: ownerAddress.String(), Balance: coins}
|
|
if bond.Balance.IsAnyGT(maxBondAmount) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Max bond amount exceeded.")
|
|
}
|
|
|
|
// Move funds into the bond account module.
|
|
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, ownerAddress, types.ModuleName, bond.Balance)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Save bond in store.
|
|
k.SaveBond(ctx, &bond)
|
|
|
|
return &bond, nil
|
|
}
|
|
|
|
// SaveBond - saves a bond to the store.
|
|
func (k Keeper) SaveBond(ctx sdk.Context, bond *types.Bond) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
// Bond ID -> Bond index.
|
|
store.Set(getBondIndexKey(bond.Id), k.cdc.MustMarshal(bond))
|
|
|
|
// Owner -> [Bond] index.
|
|
store.Set(getOwnerToBondsIndexKey(bond.Owner, bond.Id), []byte{})
|
|
}
|
|
|
|
// HasBond - checks if a bond by the given ID exists.
|
|
func (k Keeper) HasBond(ctx sdk.Context, id string) bool {
|
|
store := ctx.KVStore(k.storeKey)
|
|
return store.Has(getBondIndexKey(id))
|
|
}
|
|
|
|
// GetBond - gets a record from the store.
|
|
func (k Keeper) GetBond(ctx sdk.Context, id string) types.Bond {
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
bz := store.Get(getBondIndexKey(id))
|
|
var obj types.Bond
|
|
k.cdc.MustUnmarshal(bz, &obj)
|
|
|
|
return obj
|
|
}
|
|
|
|
// DeleteBond - deletes the bond.
|
|
func (k Keeper) DeleteBond(ctx sdk.Context, bond types.Bond) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
store.Delete(getBondIndexKey(bond.Id))
|
|
store.Delete(getOwnerToBondsIndexKey(bond.Owner, bond.Id))
|
|
}
|
|
|
|
// ListBonds - get all bonds.
|
|
func (k Keeper) ListBonds(ctx sdk.Context) []*types.Bond {
|
|
var bonds []*types.Bond
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
itr := sdk.KVStorePrefixIterator(store, prefixIDToBondIndex)
|
|
defer itr.Close()
|
|
for ; itr.Valid(); itr.Next() {
|
|
bz := store.Get(itr.Key())
|
|
if bz != nil {
|
|
var obj types.Bond
|
|
k.cdc.MustUnmarshal(bz, &obj)
|
|
bonds = append(bonds, &obj)
|
|
}
|
|
}
|
|
return bonds
|
|
}
|
|
|
|
// QueryBondsByOwner - query bonds by owner.
|
|
func (k Keeper) QueryBondsByOwner(ctx sdk.Context, ownerAddress string) []types.Bond {
|
|
var bonds []types.Bond
|
|
|
|
ownerPrefix := append(prefixOwnerToBondsIndex, []byte(ownerAddress)...) //nolint: all
|
|
store := ctx.KVStore(k.storeKey)
|
|
itr := sdk.KVStorePrefixIterator(store, ownerPrefix)
|
|
defer itr.Close()
|
|
for ; itr.Valid(); itr.Next() {
|
|
bondID := itr.Key()[len(ownerPrefix):]
|
|
bz := store.Get(append(prefixIDToBondIndex, bondID...))
|
|
if bz != nil {
|
|
var obj types.Bond
|
|
k.cdc.MustUnmarshal(bz, &obj)
|
|
bonds = append(bonds, obj)
|
|
}
|
|
}
|
|
|
|
return bonds
|
|
}
|
|
|
|
// RefillBond refills an existing bond.
|
|
func (k Keeper) RefillBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddress, coins sdk.Coins) (*types.Bond, error) {
|
|
if !k.HasBond(ctx, id) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
|
}
|
|
|
|
bond := k.GetBond(ctx, id)
|
|
if bond.Owner != ownerAddress.String() {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
|
}
|
|
|
|
// Check if account has funds.
|
|
for _, coin := range coins {
|
|
if !k.bankKeeper.HasBalance(ctx, ownerAddress, coin) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInsufficientFunds, "Insufficient funds.")
|
|
}
|
|
}
|
|
|
|
maxBondAmount := k.getMaxBondAmount(ctx)
|
|
|
|
updatedBalance := bond.Balance.Add(coins...)
|
|
if updatedBalance.IsAnyGT(maxBondAmount) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Max bond amount exceeded.")
|
|
}
|
|
|
|
// Move funds into the bond account module.
|
|
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, ownerAddress, types.ModuleName, coins)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Update bond balance and save.
|
|
bond.Balance = updatedBalance
|
|
k.SaveBond(ctx, &bond)
|
|
|
|
return &bond, nil
|
|
}
|
|
|
|
// WithdrawBond withdraws funds from a bond.
|
|
func (k Keeper) WithdrawBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddress, coins sdk.Coins) (*types.Bond, error) {
|
|
if !k.HasBond(ctx, id) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
|
}
|
|
|
|
bond := k.GetBond(ctx, id)
|
|
if bond.Owner != ownerAddress.String() {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
|
}
|
|
|
|
updatedBalance, isNeg := bond.Balance.SafeSub(coins...)
|
|
if isNeg {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInsufficientFunds, "Insufficient bond balance.")
|
|
}
|
|
|
|
// Move funds from the bond into the account.
|
|
err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, ownerAddress, coins)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Update bond balance and save.
|
|
bond.Balance = updatedBalance
|
|
k.SaveBond(ctx, &bond)
|
|
|
|
return &bond, nil
|
|
}
|
|
|
|
// CancelBond cancels a bond, returning funds to the owner.
|
|
func (k Keeper) CancelBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddress) (*types.Bond, error) {
|
|
if !k.HasBond(ctx, id) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
|
}
|
|
|
|
bond := k.GetBond(ctx, id)
|
|
if bond.Owner != ownerAddress.String() {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
|
}
|
|
|
|
// Check if bond is used in other modules.
|
|
for _, usageKeeper := range k.usageKeepers {
|
|
if usageKeeper.UsesBond(ctx, id) {
|
|
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprintf("Bond in use by the '%s' module.", usageKeeper.ModuleName()))
|
|
}
|
|
}
|
|
|
|
// Move funds from the bond into the account.
|
|
err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, ownerAddress, bond.Balance)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k.DeleteBond(ctx, bond)
|
|
|
|
return &bond, nil
|
|
}
|
|
|
|
func (k Keeper) getMaxBondAmount(ctx sdk.Context) sdk.Coins {
|
|
params := k.GetParams(ctx)
|
|
maxBondAmount := params.MaxBondAmount
|
|
return sdk.NewCoins(maxBondAmount)
|
|
}
|
|
|
|
// GetBondModuleBalances gets the bond module account(s) balances.
|
|
func (k Keeper) GetBondModuleBalances(ctx sdk.Context) sdk.Coins {
|
|
moduleAddress := k.accountKeeper.GetModuleAddress(types.ModuleName)
|
|
balances := k.bankKeeper.GetAllBalances(ctx, moduleAddress)
|
|
return balances
|
|
}
|
|
|
|
// TransferCoinsToModuleAccount moves funds from the bonds module account to another module account.
|
|
func (k Keeper) TransferCoinsToModuleAccount(ctx sdk.Context, id, moduleAccount string, coins sdk.Coins) error {
|
|
if !k.HasBond(ctx, id) {
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond not found.")
|
|
}
|
|
|
|
bondObj := k.GetBond(ctx, id)
|
|
|
|
// Deduct rent from bond.
|
|
updatedBalance, isNeg := bondObj.Balance.SafeSub(coins...)
|
|
|
|
if isNeg {
|
|
// Check if bond has sufficient funds.
|
|
return errorsmod.Wrap(sdkerrors.ErrInsufficientFunds, "Insufficient funds.")
|
|
}
|
|
|
|
// Move funds from bond module to record rent module.
|
|
err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, moduleAccount, coins)
|
|
if err != nil {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Error transferring funds.")
|
|
}
|
|
|
|
// Update bond balance.
|
|
bondObj.Balance = updatedBalance
|
|
k.SaveBond(ctx, &bondObj)
|
|
|
|
return nil
|
|
}
|