e90b21bc8e
1. add bond,auction, nameserivce module 2. update to v0.12.2 ethermint version 3. fix the test cases 4. add gql server
356 lines
10 KiB
Go
356 lines
10 KiB
Go
package keeper
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
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"
|
|
"github.com/tharsis/ethermint/x/bond/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 sdk.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 sdk.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, sdkerrors.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, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Invalid max bond amount.")
|
|
}
|
|
|
|
bond := types.Bond{Id: bondID, Owner: ownerAddress.String(), Balance: coins}
|
|
if bond.Balance.IsAnyGT(maxBondAmount) {
|
|
return nil, sdkerrors.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)...)
|
|
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, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
|
}
|
|
|
|
bond := k.GetBond(ctx, id)
|
|
if bond.Owner != ownerAddress.String() {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
|
}
|
|
|
|
// Check if account has funds.
|
|
for _, coin := range coins {
|
|
if !k.bankKeeper.HasBalance(ctx, ownerAddress, coin) {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "Insufficient funds.")
|
|
}
|
|
}
|
|
|
|
maxBondAmount, err := k.getMaxBondAmount(ctx)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Invalid max bond amount.")
|
|
}
|
|
|
|
updatedBalance := bond.Balance.Add(coins...)
|
|
if updatedBalance.IsAnyGT(maxBondAmount) {
|
|
return nil, sdkerrors.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, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
|
}
|
|
|
|
bond := k.GetBond(ctx, id)
|
|
if bond.Owner != ownerAddress.String() {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.")
|
|
}
|
|
|
|
updatedBalance, isNeg := bond.Balance.SafeSub(coins)
|
|
if isNeg {
|
|
return nil, sdkerrors.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, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Bond not found.")
|
|
}
|
|
|
|
bond := k.GetBond(ctx, id)
|
|
if bond.Owner != ownerAddress.String() {
|
|
return nil, sdkerrors.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, sdkerrors.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, error) {
|
|
params := k.GetParams(ctx)
|
|
maxBondAmount := params.MaxBondAmount
|
|
return sdk.NewCoins(maxBondAmount), nil
|
|
}
|
|
|
|
// 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 sdkerrors.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 sdkerrors.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 sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Error transferring funds.")
|
|
}
|
|
|
|
// Update bond balance.
|
|
bondObj.Balance = updatedBalance
|
|
k.SaveBond(ctx, &bondObj)
|
|
|
|
return nil
|
|
}
|