0g-chain/x/liquid/keeper/staking.go
Denali Marsh ceaed3f0e1
liquid staking (#1273)
* proto types

* proto generated types

* liquidstaking top level files: module, genesis

* liquidstaking types

* liquidstaking keeper

* liquidstaking client/cli

* add liquidstaking to app, simapp

* implement mint derivative

* set up liquidstaking keeper test suite

* test mint derivative

* rename module to liquid

* rename proto types, app.go to liquid

* use sdk.Coin instead of shares

* mint liquid tokens to delegator

* burn derivative tokens to receive delegation

* use conversion method instead of type cast

* simplify delegation transfer logic

* broaden delegation transfer tests

* simplify transfer delegation method
This removes a source of rounding errors

* move derivative denom to keeper config

* check for invalid coins in msg validation

* block 0share transfers to avoid handling edge case

* refactor MintDerivative to test calculations

* simplify burn method so shares and tokens equal

* convert TransferShares back to old design
this makes handling vesting tokens easier

* fix missed merge conflict

* remove deprecated constants

* tidy up msg.go

* add msg tests

* remove unused store key

* fix msg event sender

* remove unused params

* tidy up documentation and errors

* remove unused mocks

* remove unused keepers from AppModule

* tidy up msg return values
keeper return values to be used in router msgs

* reinstate unintentionally removed interface check

* catch invalid input for MnitDerivative
clear up test TODOs

* clear up InitGenesis TODO

* Update x/liquid/client/cli/tx.go

Co-authored-by: Derrick Lee <derrick@dlee.dev>

* Update x/liquid/client/cli/tx.go

Co-authored-by: Derrick Lee <derrick@dlee.dev>

* show error logs in devnet

* unblock mod account so it can receive dist rewards

* catch zero amout msgs early

* minor cli fixes

Co-authored-by: rhuairahrighairigh <ruaridh.odonnell@gmail.com>
Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
Co-authored-by: Derrick Lee <derrick@dlee.dev>
2022-09-15 23:00:32 +01:00

110 lines
4.5 KiB
Go

package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/kava-labs/kava/x/liquid/types"
)
// TransferDelegation moves some delegation shares between addresses, while keeping the same validator.
//
// Internally shares are unbonded, tokens moved then bonded again. This limits only vested tokens from being transferred.
// The sending delegation must not have any active redelegations.
// A validator cannot reduce self delegated shares below its min self delegation.
// Attempting to transfer zero shares will error.
func (k Keeper) TransferDelegation(ctx sdk.Context, valAddr sdk.ValAddress, fromDelegator, toDelegator sdk.AccAddress, shares sdk.Dec) (sdk.Dec, error) {
// Redelegations link a delegation to it's previous validator so slashes are propagated to the new validator.
// If the delegation is transferred to a new owner, the redelegation object must be updated.
// For expediency all transfers with redelegations are blocked.
if k.stakingKeeper.HasReceivingRedelegation(ctx, fromDelegator, valAddr) {
return sdk.Dec{}, types.ErrRedelegationsNotCompleted
}
if shares.IsNil() || shares.LT(sdk.ZeroDec()) {
return sdk.Dec{}, sdkerrors.Wrap(types.ErrUntransferableShares, "nil or negative shares")
}
if shares.Equal(sdk.ZeroDec()) {
// Block 0 transfers to reduce edge cases.
return sdk.Dec{}, sdkerrors.Wrap(types.ErrUntransferableShares, "zero shares")
}
fromDelegation, found := k.stakingKeeper.GetDelegation(ctx, fromDelegator, valAddr)
if !found {
return sdk.Dec{}, types.ErrNoDelegatorForAddress
}
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdk.Dec{}, types.ErrNoValidatorFound
}
// Prevent validators from reducing their self delegation below the min.
isValidatorOperator := fromDelegator.Equals(valAddr)
if isValidatorOperator {
if isBelowMinSelfDelegation(validator, fromDelegation.Shares.Sub(shares)) {
return sdk.Dec{}, types.ErrSelfDelegationBelowMinimum
}
}
returnAmount, err := k.fastUndelegate(ctx, valAddr, fromDelegator, shares)
if err != nil {
return sdk.Dec{}, err
}
returnCoins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
if err := k.bankKeeper.SendCoins(ctx, fromDelegator, toDelegator, returnCoins); err != nil {
return sdk.Dec{}, err
}
receivedShares, err := k.delegateFromAccount(ctx, valAddr, toDelegator, returnAmount)
if err != nil {
return sdk.Dec{}, err
}
return receivedShares, nil
}
// isBelowMinSelfDelegation check if the supplied shares, converted to tokens, are under the validator's min_self_delegation.
func isBelowMinSelfDelegation(validator stakingtypes.ValidatorI, shares sdk.Dec) bool {
return validator.TokensFromShares(shares).TruncateInt().LT(validator.GetMinSelfDelegation())
}
// fastUndelegate undelegates shares from a validator skipping the unbonding period and not creating any unbonding delegations.
func (k Keeper) fastUndelegate(ctx sdk.Context, valAddr sdk.ValAddress, delegator sdk.AccAddress, shares sdk.Dec) (sdk.Int, error) {
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdk.Int{}, types.ErrNoDelegatorForAddress
}
returnAmount, err := k.stakingKeeper.Unbond(ctx, delegator, valAddr, shares)
if err != nil {
return sdk.Int{}, err
}
returnCoins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
// transfer the validator tokens to the not bonded pool
if validator.IsBonded() {
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, returnCoins); err != nil {
panic(err)
}
}
if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(ctx, stakingtypes.NotBondedPoolName, delegator, returnCoins); err != nil {
return sdk.Int{}, err
}
return returnAmount, nil
}
// delegateFromAccount delegates to a validator from an account (vs redelegating from an existing delegation)
func (k Keeper) delegateFromAccount(ctx sdk.Context, valAddr sdk.ValAddress, delegator sdk.AccAddress, amount sdk.Int) (sdk.Dec, error) {
validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
if !found {
return sdk.Dec{}, types.ErrNoValidatorFound
}
// source tokens are from an account, so subtractAccount true and tokenSrc unbonded
newShares, err := k.stakingKeeper.Delegate(ctx, delegator, amount, stakingtypes.Unbonded, validator, true)
if err != nil {
return sdk.Dec{}, err
}
return newShares, nil
}