Additional bond commands (#4)

* Add commands to get bonds by id and owner

* Add commands to get bond module params and balances

* Add commands to refill, withdraw and cancel bond

* Add implementations for bond tx commands

* Use indexed map to implement command for getting bond by owner

* Use collections for bond module params

* Clean up
This commit is contained in:
prathamesh0
2024-02-08 18:53:20 +05:30
committed by GitHub
parent e511051f3e
commit 4da8dd8d7b
22 changed files with 11740 additions and 262 deletions
+7 -2
View File
@@ -7,7 +7,9 @@ import (
// InitGenesis initializes the module state from a genesis state.
func (k *Keeper) InitGenesis(ctx sdk.Context, data *bond.GenesisState) error {
k.SetParams(ctx, data.Params)
if err := k.Params.Set(ctx, data.Params); err != nil {
return err
}
// Save bonds in store.
for _, bond := range data.Bonds {
@@ -21,7 +23,10 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, data *bond.GenesisState) error {
// ExportGenesis exports the module state to a genesis state.
func (k *Keeper) ExportGenesis(ctx sdk.Context) (*bond.GenesisState, error) {
params := k.GetParams(ctx)
params, err := k.Params.Get(ctx)
if err != nil {
return nil, err
}
bonds, err := k.ListBonds(ctx)
if err != nil {
+262 -52
View File
@@ -3,9 +3,11 @@ package keeper
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"cosmossdk.io/collections"
"cosmossdk.io/collections/indexes"
"cosmossdk.io/core/store"
errorsmod "cosmossdk.io/errors"
@@ -18,6 +20,26 @@ import (
bondtypes "git.vdb.to/cerc-io/laconic2d/x/bond"
)
type BondsIndexes struct {
Owner *indexes.Multi[string, string, bondtypes.Bond]
}
func (b BondsIndexes) IndexesList() []collections.Index[string, bondtypes.Bond] {
return []collections.Index[string, bondtypes.Bond]{b.Owner}
}
func newBondIndexes(sb *collections.SchemaBuilder) BondsIndexes {
return BondsIndexes{
Owner: indexes.NewMulti(
sb, bondtypes.BondOwnerIndexPrefix, "bonds_by_owner",
collections.StringKey, collections.StringKey,
func(_ string, v bondtypes.Bond) (string, error) {
return v.Owner, nil
},
),
}
}
type Keeper struct {
// Codecs
cdc codec.BinaryCodec
@@ -29,11 +51,10 @@ type Keeper struct {
// Track bond usage in other cosmos-sdk modules (more like a usage tracker).
// usageKeepers []types.BondUsageKeeper
// paramSubspace paramtypes.Subspace
// State management
Schema collections.Schema
Bonds collections.Map[string, bondtypes.Bond]
Params collections.Item[bondtypes.Params]
Bonds *collections.IndexedMap[string, bondtypes.Bond, BondsIndexes]
}
// NewKeeper creates new instances of the bond Keeper
@@ -43,21 +64,15 @@ func NewKeeper(
accountKeeper auth.AccountKeeper,
bankKeeper bank.Keeper,
// usageKeepers []types.BondUsageKeeper,
// ps paramtypes.Subspace,
) Keeper {
// set KeyTable if it has not already been set
// if !ps.HasKeyTable() {
// ps = ps.WithKeyTable(types.ParamKeyTable())
// }
sb := collections.NewSchemaBuilder(storeService)
k := Keeper{
cdc: cdc,
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
Bonds: collections.NewMap(sb, bondtypes.BondsKey, "bonds", collections.StringKey, codec.CollValue[bondtypes.Bond](cdc)),
Params: collections.NewItem(sb, bondtypes.ParamsKeyPrefix, "params", codec.CollValue[bondtypes.Params](cdc)),
Bonds: collections.NewIndexedMap(sb, bondtypes.BondsKeyPrefix, "bonds", collections.StringKey, codec.CollValue[bondtypes.Bond](cdc), newBondIndexes(sb)),
// usageKeepers: usageKeepers,
// paramSubspace: ps,
}
schema, err := sb.Build()
@@ -70,58 +85,39 @@ func NewKeeper(
return k
}
// BondID simplifies generation of bond IDs.
type BondID struct {
// 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 {
// 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)
str := fmt.Sprintf("%s:%d:%d", bondId.Address.String(), bondId.AccNum, bondId.Sequence)
hasher.Write([]byte(str))
return hex.EncodeToString(hasher.Sum(nil))
}
// CreateBond creates a new bond.
func (k Keeper) CreateBond(ctx sdk.Context, ownerAddress sdk.AccAddress, coins sdk.Coins) (*bondtypes.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 := bondtypes.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, bondtypes.ModuleName, bond.Balance)
// HasBond - checks if a bond by the given Id exists.
func (k Keeper) HasBond(ctx sdk.Context, id string) (bool, error) {
has, err := k.Bonds.Has(ctx, id)
if err != nil {
return nil, err
return false, err
}
// Save bond in store.
if err := k.Bonds.Set(ctx, bond.Id, bond); err != nil {
return nil, err
}
return has, nil
}
return &bond, nil
// SaveBond - saves a bond to the store.
func (k Keeper) SaveBond(ctx sdk.Context, bond *bondtypes.Bond) error {
return k.Bonds.Set(ctx, bond.Id, *bond)
}
// DeleteBond - deletes the bond.
func (k Keeper) DeleteBond(ctx sdk.Context, bond bondtypes.Bond) error {
return k.Bonds.Remove(ctx, bond.Id)
}
// ListBonds - get all bonds.
@@ -145,8 +141,222 @@ func (k Keeper) ListBonds(ctx sdk.Context) ([]*bondtypes.Bond, error) {
return bonds, nil
}
func (k Keeper) getMaxBondAmount(ctx sdk.Context) sdk.Coins {
params := k.GetParams(ctx)
func (k Keeper) GetBondById(ctx sdk.Context, id string) (bondtypes.Bond, error) {
bond, err := k.Bonds.Get(ctx, id)
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return bondtypes.Bond{}, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
return bondtypes.Bond{}, err
}
return bond, nil
}
func (k Keeper) GetBondsByOwner(ctx sdk.Context, owner string) ([]bondtypes.Bond, error) {
iter, err := k.Bonds.Indexes.Owner.MatchExact(ctx, owner)
if err != nil {
return []bondtypes.Bond{}, err
}
return indexes.CollectValues(ctx, k.Bonds, iter)
}
// GetBondModuleBalances gets the bond module account(s) balances.
func (k Keeper) GetBondModuleBalances(ctx sdk.Context) sdk.Coins {
moduleAddress := k.accountKeeper.GetModuleAddress(bondtypes.ModuleName)
balances := k.bankKeeper.GetAllBalances(ctx, moduleAddress)
return balances
}
// CreateBond creates a new bond.
func (k Keeper) CreateBond(ctx sdk.Context, ownerAddress sdk.AccAddress, coins sdk.Coins) (*bondtypes.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, err := k.getMaxBondAmount(ctx)
if err != nil {
return nil, err
}
bond := bondtypes.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, bondtypes.ModuleName, bond.Balance)
if err != nil {
return nil, err
}
// Save bond in store.
err = k.SaveBond(ctx, &bond)
if err != nil {
return nil, err
}
return &bond, nil
}
func (k Keeper) RefillBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddress, coins sdk.Coins) (*bondtypes.Bond, error) {
if has, err := k.HasBond(ctx, id); !has {
if err != nil {
return nil, err
}
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
bond, err := k.GetBondById(ctx, id)
if err != nil {
return nil, err
}
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, err := k.getMaxBondAmount(ctx)
if err != nil {
return nil, err
}
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, bondtypes.ModuleName, coins)
if err != nil {
return nil, err
}
// Update bond balance and save.
bond.Balance = updatedBalance
err = k.SaveBond(ctx, &bond)
if err != nil {
return nil, err
}
return &bond, nil
}
func (k Keeper) WithdrawBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddress, coins sdk.Coins) (*bondtypes.Bond, error) {
if has, err := k.HasBond(ctx, id); !has {
if err != nil {
return nil, err
}
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
bond, err := k.GetBondById(ctx, id)
if err != nil {
return nil, err
}
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, bondtypes.ModuleName, ownerAddress, coins)
if err != nil {
return nil, err
}
// Update bond balance and save.
bond.Balance = updatedBalance
err = k.SaveBond(ctx, &bond)
if err != nil {
return nil, err
}
return &bond, nil
}
func (k Keeper) CancelBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddress) (*bondtypes.Bond, error) {
if has, err := k.HasBond(ctx, id); !has {
if err != nil {
return nil, err
}
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
}
bond, err := k.GetBondById(ctx, id)
if err != nil {
return nil, err
}
if bond.Owner != ownerAddress.String() {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
}
// TODO
// 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, bondtypes.ModuleName, ownerAddress, bond.Balance)
if err != nil {
return nil, err
}
// Remove bond from store.
err = k.DeleteBond(ctx, bond)
if err != nil {
return nil, err
}
return &bond, nil
}
// GetParams gets the bond module's parameters.
func (k Keeper) GetParams(ctx sdk.Context) (*bondtypes.Params, error) {
params, err := k.Params.Get(ctx)
if err != nil {
return nil, err
}
return &params, nil
}
func (k Keeper) getMaxBondAmount(ctx sdk.Context) (sdk.Coins, error) {
params, err := k.GetParams(ctx)
if err != nil {
return nil, err
}
maxBondAmount := params.MaxBondAmount
return sdk.NewCoins(maxBondAmount)
return sdk.NewCoins(maxBondAmount), nil
}
+91 -3
View File
@@ -12,7 +12,6 @@ type msgServer struct {
k Keeper
}
// TODO: Generate types
var _ bond.MsgServer = msgServer{}
// NewMsgServerImpl returns an implementation of the module MsgServer interface.
@@ -20,10 +19,9 @@ func NewMsgServerImpl(keeper Keeper) bond.MsgServer {
return &msgServer{k: keeper}
}
// TODO: Add remaining write methods
func (ms msgServer) CreateBond(c context.Context, msg *bond.MsgCreateBond) (*bond.MsgCreateBondResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
signerAddress, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return nil, err
@@ -48,3 +46,93 @@ func (ms msgServer) CreateBond(c context.Context, msg *bond.MsgCreateBond) (*bon
return &bond.MsgCreateBondResponse{}, nil
}
// RefillBond implements bond.MsgServer.
func (ms msgServer) RefillBond(c context.Context, msg *bond.MsgRefillBond) (*bond.MsgRefillBondResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
signerAddress, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return nil, err
}
_, err = ms.k.RefillBond(ctx, msg.Id, signerAddress, msg.Coins)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
bond.EventTypeRefillBond,
sdk.NewAttribute(bond.AttributeKeySigner, msg.Signer),
sdk.NewAttribute(bond.AttributeKeyBondId, msg.Id),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Coins.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, bond.AttributeValueCategory),
sdk.NewAttribute(bond.AttributeKeySigner, msg.Signer),
),
})
return &bond.MsgRefillBondResponse{}, nil
}
// WithdrawBond implements bond.MsgServer.
func (ms msgServer) WithdrawBond(c context.Context, msg *bond.MsgWithdrawBond) (*bond.MsgWithdrawBondResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
signerAddress, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return nil, err
}
_, err = ms.k.WithdrawBond(ctx, msg.Id, signerAddress, msg.Coins)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
bond.EventTypeWithdrawBond,
sdk.NewAttribute(bond.AttributeKeySigner, msg.Signer),
sdk.NewAttribute(bond.AttributeKeyBondId, msg.Id),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Coins.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, bond.AttributeValueCategory),
sdk.NewAttribute(bond.AttributeKeySigner, msg.Signer),
),
})
return &bond.MsgWithdrawBondResponse{}, nil
}
// CancelBond implements bond.MsgServer.
func (ms msgServer) CancelBond(c context.Context, msg *bond.MsgCancelBond) (*bond.MsgCancelBondResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
signerAddress, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return nil, err
}
_, err = ms.k.CancelBond(ctx, msg.Id, signerAddress)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
bond.EventTypeCancelBond,
sdk.NewAttribute(bond.AttributeKeySigner, msg.Signer),
sdk.NewAttribute(bond.AttributeKeyBondId, msg.Id),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, bond.AttributeValueCategory),
sdk.NewAttribute(bond.AttributeKeySigner, msg.Signer),
),
})
return &bond.MsgCancelBondResponse{}, nil
}
-23
View File
@@ -1,23 +0,0 @@
package keeper
import (
"git.vdb.to/cerc-io/laconic2d/x/bond"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GetMaxBondAmount max bond amount
func (k Keeper) GetMaxBondAmount(ctx sdk.Context) (res sdk.Coin) {
// TODO: Implement
return sdk.NewCoin(sdk.DefaultBondDenom, bond.DefaultMaxBondAmountTokens)
}
// GetParams - Get all parameter as types.Params.
func (k Keeper) GetParams(ctx sdk.Context) (params bond.Params) {
getMaxBondAmount := k.GetMaxBondAmount(ctx)
return bond.Params{MaxBondAmount: getMaxBondAmount}
}
// SetParams - set the params.
func (k Keeper) SetParams(ctx sdk.Context, params bond.Params) {
// TODO: Implement
}
+63 -7
View File
@@ -3,24 +3,38 @@ package keeper
import (
"context"
"git.vdb.to/cerc-io/laconic2d/x/bond"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
)
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
// TODO: Add remaining query methods
bondtypes "git.vdb.to/cerc-io/laconic2d/x/bond"
)
type queryServer struct {
k Keeper
}
var _ bond.QueryServer = queryServer{}
var _ bondtypes.QueryServer = queryServer{}
// NewQueryServerImpl returns an implementation of the module QueryServer.
func NewQueryServerImpl(k Keeper) bond.QueryServer {
func NewQueryServerImpl(k Keeper) bondtypes.QueryServer {
return queryServer{k}
}
func (qs queryServer) Bonds(c context.Context, _ *bond.QueryGetBondsRequest) (*bond.QueryGetBondsResponse, error) {
// Params implements bond.QueryServer.
func (qs queryServer) Params(c context.Context, _ *bondtypes.QueryParamsRequest) (*bondtypes.QueryParamsResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
params, err := qs.k.GetParams(ctx)
if err != nil {
return nil, err
}
return &bondtypes.QueryParamsResponse{Params: params}, nil
}
// Bonds implements bond.QueryServer.
func (qs queryServer) Bonds(c context.Context, _ *bondtypes.QueryGetBondsRequest) (*bondtypes.QueryGetBondsResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
resp, err := qs.k.ListBonds(ctx)
@@ -28,5 +42,47 @@ func (qs queryServer) Bonds(c context.Context, _ *bond.QueryGetBondsRequest) (*b
return nil, err
}
return &bond.QueryGetBondsResponse{Bonds: resp}, nil
return &bondtypes.QueryGetBondsResponse{Bonds: resp}, nil
}
// GetBondById implements bond.QueryServer.
func (qs queryServer) GetBondById(c context.Context, req *bondtypes.QueryGetBondByIdRequest) (*bondtypes.QueryGetBondByIdResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
bondId := req.GetId()
if len(bondId) == 0 {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "bond id required")
}
bond, err := qs.k.GetBondById(ctx, bondId)
if err != nil {
return nil, err
}
return &bondtypes.QueryGetBondByIdResponse{Bond: &bond}, nil
}
// GetBondsByOwner implements bond.QueryServer.
func (qs queryServer) GetBondsByOwner(c context.Context, req *bondtypes.QueryGetBondsByOwnerRequest) (*bondtypes.QueryGetBondsByOwnerResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
owner := req.GetOwner()
if len(owner) == 0 {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "owner required")
}
bonds, err := qs.k.GetBondsByOwner(ctx, owner)
if err != nil {
return nil, err
}
return &bondtypes.QueryGetBondsByOwnerResponse{Bonds: bonds}, nil
}
// GetBondsModuleBalance implements bond.QueryServer.
func (qs queryServer) GetBondsModuleBalance(c context.Context, _ *bondtypes.QueryGetBondModuleBalanceRequest) (*bondtypes.QueryGetBondModuleBalanceResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
balances := qs.k.GetBondModuleBalances(ctx)
return &bondtypes.QueryGetBondModuleBalanceResponse{Balance: balances}, nil
}