Skip to content

Commit

Permalink
Merge pull request #85 from crescent-network/84-fix-and-update-liquid…
Browse files Browse the repository at this point in the history
…staking

fix: fix and update liquidstaking
  • Loading branch information
dongsam authored Jan 19, 2022
2 parents 46ae0c0 + e069093 commit f6320ec
Show file tree
Hide file tree
Showing 19 changed files with 664 additions and 184 deletions.
7 changes: 4 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ func NewCrescentApp(
params.NewAppModule(app.ParamsKeeper),
liquidity.NewAppModule(appCodec, app.LiquidityKeeper),
farming.NewAppModule(appCodec, app.FarmingKeeper, app.AccountKeeper, app.BankKeeper),
liquidstaking.NewAppModule(appCodec, app.LiquidStakingKeeper, app.AccountKeeper, app.BankKeeper),
liquidstaking.NewAppModule(appCodec, app.LiquidStakingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.DistrKeeper, app.GovKeeper),
app.transferModule,
)

Expand Down Expand Up @@ -598,7 +598,7 @@ func NewCrescentApp(
params.NewAppModule(app.ParamsKeeper),
evidence.NewAppModule(app.EvidenceKeeper),
liquidity.NewAppModule(appCodec, app.LiquidityKeeper),
liquidstaking.NewAppModule(appCodec, app.LiquidStakingKeeper, app.AccountKeeper, app.BankKeeper),
liquidstaking.NewAppModule(appCodec, app.LiquidStakingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.DistrKeeper, app.GovKeeper),
ibc.NewAppModule(app.IBCKeeper),
app.transferModule,
)
Expand Down Expand Up @@ -680,7 +680,8 @@ func (app *CrescentApp) ModuleAccountAddrs() map[string]bool {

// add farming, liquidstaking proxy account
modAccAddrs[farmingtypes.RewardsReserveAcc.String()] = true
modAccAddrs[liquidstakingtypes.LiquidStakingProxyAcc.String()] = true
// TODO: temporary removed for withdraw rewards
//modAccAddrs[liquidstakingtypes.LiquidStakingProxyAcc.String()] = true

return modAccAddrs
}
Expand Down
14 changes: 8 additions & 6 deletions proto/crescent/liquidstaking/v1beta1/liquidstaking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ message WhitelistedValidator {

// weight specifies the weight for liquid staking, unstaking amount
string weight = 2 [
(gogoproto.moretags) = "yaml:\"weight\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
(gogoproto.moretags) = "yaml:\"weight\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"10\"", format: "sdk.Int"}
];
}

Expand All @@ -89,8 +90,9 @@ message LiquidValidator {

// weight specifies the weight for liquid staking, unstaking amount
string weight = 4 [
(gogoproto.moretags) = "yaml:\"weight\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
(gogoproto.moretags) = "yaml:\"weight\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"10\"", format: "sdk.Int"}
];
}
11 changes: 7 additions & 4 deletions x/liquidstaking/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import (
// BeginBlocker collects liquidStakings for the current block
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
// TODO: Unimplemented beginblock logic
//if err != nil {
// panic(err)
//}
// Withdraw rewards of LiquidStakingProxyAcc and re-staking
totalRewards := k.WithdrawLiquidRewards(ctx, types.LiquidStakingProxyAcc)
// TODO: consider re-staking with balance
_, err := k.LiquidDelegate(ctx, types.LiquidStakingProxyAcc, k.GetActiveLiquidValidators(ctx), totalRewards)
if err != nil {
panic(err)
}
}

func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
Expand Down
5 changes: 3 additions & 2 deletions x/liquidstaking/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ func (h Hooks) AfterProposalVotingPeriodEnded(_ sdk.Context, _ uint64) {

// GetOtherVotes calculate the voting power of the person who participated in liquid staking.
func (h Hooks) GetOtherVotes(ctx sdk.Context, votes *govtypes.Votes, otherVotes *govtypes.OtherVotes) {
liquidVals, _ := h.k.GetActiveLiquidValidators(ctx)
lenLiquidVals := len(liquidVals)
liquidVals := h.k.GetActiveLiquidValidators(ctx)
lenLiquidVals := liquidVals.Len()
liquidBondDenom := h.k.LiquidBondDenom(ctx)
totalSupply := h.k.bankKeeper.GetSupply(ctx, liquidBondDenom).Amount.ToDec()
if totalSupply.IsPositive() {
Expand All @@ -39,6 +39,7 @@ func (h Hooks) GetOtherVotes(ctx sdk.Context, votes *govtypes.Votes, otherVotes
// TODO: exchange rate for native token, netAmount function
if lTokenBalance.IsPositive() {
(*otherVotes)[vote.Voter] = map[string]sdk.Dec{}
// TODO: apply weighted dividedPower
dividedPower := lTokenBalance.QuoTruncate(sdk.NewDec(int64(lenLiquidVals)))
for _, val := range liquidVals {
if existed, ok := (*otherVotes)[vote.Voter][val.OperatorAddress]; ok {
Expand Down
2 changes: 1 addition & 1 deletion x/liquidstaking/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type Keeper struct {
cdc codec.BinaryCodec
paramSpace paramtypes.Subspace

bankKeeper types.BankKeeper
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
stakingKeeper types.StakingKeeper
distrKeeper types.Distrkeeper
govKeeper types.GovKeeper
Expand Down
34 changes: 11 additions & 23 deletions x/liquidstaking/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

simapp "github.com/crescent-network/crescent/app"
"github.com/crescent-network/crescent/x/liquidstaking/keeper"
"github.com/crescent-network/crescent/x/liquidstaking/types"
)

var (
Expand All @@ -40,7 +38,7 @@ type KeeperTestSuite struct {
valAddrs []sdk.ValAddress
//sourceAddrs []sdk.AccAddress
//destinationAddrs []sdk.AccAddress
whitelistedValidators []types.WhitelistedValidator
//whitelistedValidators []types.WhitelistedValidator
}

//func testProposal(changes ...proposal.ParamChange) *proposal.ParameterChangeProposal {
Expand Down Expand Up @@ -87,12 +85,12 @@ func (suite *KeeperTestSuite) SetupTest() {
//err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, suite.sourceAddrs[3], smallBalances)
//suite.Require().NoError(err)

suite.whitelistedValidators = []types.WhitelistedValidator{
{
ValidatorAddress: "cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv",
Weight: sdk.MustNewDecFromStr("0.5"),
},
}
//suite.whitelistedValidators = []types.WhitelistedValidator{
// {
// ValidatorAddress: "cosmosvaloper10e4vsut6suau8tk9m6dnrm0slgd6npe3jx5xpv",
// Weight: sdk.MustNewDecFromStr("0.5"),
// },
//}
}

//func coinsEq(exp, got sdk.Coins) (bool, string, string, string) {
Expand All @@ -108,20 +106,11 @@ func (suite *KeeperTestSuite) SetupTest() {
//}

func (suite *KeeperTestSuite) CreateValidators(powers []int64) ([]sdk.AccAddress, []sdk.ValAddress) {
suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{})
num := len(powers)
addrs := simapp.AddTestAddrsIncremental(suite.app, suite.ctx, num, sdk.NewInt(1000000000))
valAddrs := simapp.ConvertAddrsToValAddrs(addrs)
pks := simapp.CreateTestPubKeys(num)
cdc := simapp.MakeTestEncodingConfig().Marshaler

stakingKeeper := stakingkeeper.NewKeeper(
cdc,
suite.app.GetKey(stakingtypes.StoreKey),
suite.app.AccountKeeper,
suite.app.BankKeeper,
suite.app.GetSubspace(stakingtypes.ModuleName),
)
suite.app.StakingKeeper = &stakingKeeper

for i, power := range powers {
val, err := stakingtypes.NewValidator(valAddrs[i], pks[i], stakingtypes.Description{})
Expand All @@ -130,13 +119,12 @@ func (suite *KeeperTestSuite) CreateValidators(powers []int64) ([]sdk.AccAddress
err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, val)
suite.Require().NoError(err)
suite.app.StakingKeeper.SetNewValidatorByPowerIndex(suite.ctx, val)
suite.app.DistrKeeper.Hooks().AfterValidatorCreated(suite.ctx, val.GetOperator())
suite.app.SlashingKeeper.Hooks().AfterValidatorCreated(suite.ctx, val.GetOperator())
suite.app.StakingKeeper.AfterValidatorCreated(suite.ctx, val.GetOperator())
newShares, err := suite.app.StakingKeeper.Delegate(suite.ctx, addrs[i], sdk.NewInt(power), stakingtypes.Unbonded, val, true)
suite.Require().NoError(err)
suite.Require().Equal(newShares.TruncateInt(), sdk.NewInt(power))
}

_ = staking.EndBlocker(suite.ctx, *suite.app.StakingKeeper)
suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
return addrs, valAddrs
}
124 changes: 81 additions & 43 deletions x/liquidstaking/keeper/liquidstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@ func (k Keeper) NetAmount(ctx sdk.Context) sdk.Dec {
return balance.Amount.ToDec().Add(liquidPower).Add(totalRewards).Add(unbondingPower.ToDec())
}

func (k Keeper) LiquidDelegate(ctx sdk.Context, proxyAcc sdk.AccAddress, activeVals types.LiquidValidators, stakingAmt sdk.Int) (newShares sdk.Dec, err error) {
totalNewShares := sdk.ZeroDec()
targetMap := types.AddStakingTargetMap(activeVals, stakingAmt)
for valStr, amt := range targetMap {
val, err := sdk.ValAddressFromBech32(valStr)
if err != nil {
return sdk.ZeroDec(), err
}
validator, found := k.stakingKeeper.GetValidator(ctx, val)
if !found {
panic("validator not founded")
}
// TODO: consider checking active val
newShares, err = k.stakingKeeper.Delegate(ctx, proxyAcc, amt, stakingtypes.Unbonded, validator, true)
if err != nil {
return sdk.ZeroDec(), err
}
// TODO: consider kv optimize
liquidVal, found := k.GetLiquidValidator(ctx, val)
if !found {
panic("liquid validator not founded")
}
liquidVal.LiquidTokens = liquidVal.LiquidTokens.Add(amt)
k.SetLiquidValidator(ctx, liquidVal)
totalNewShares = totalNewShares.Add(newShares)
}
return totalNewShares, nil
}

// LiquidStaking ...
func (k Keeper) LiquidStaking(
ctx sdk.Context, proxyAcc, liquidStaker sdk.AccAddress, stakingCoin sdk.Coin) (newShares sdk.Dec, err error) {
Expand All @@ -62,6 +91,11 @@ func (k Keeper) LiquidStaking(
)
}

activeVals := k.GetActiveLiquidValidators(ctx)
if activeVals.Len() == 0 || !activeVals.TotalWeight().IsPositive() {
return sdk.ZeroDec(), fmt.Errorf("there's no active liquid validators")
}

netAmount := k.NetAmount(ctx)

// send staking coin to liquid staking proxy account to proxy delegation
Expand All @@ -74,9 +108,8 @@ func (k Keeper) LiquidStaking(
liquidBondDenom := k.LiquidBondDenom(ctx)
bTokenTotalSupply := k.bankKeeper.GetSupply(ctx, liquidBondDenom)
mintAmt := stakingCoin.Amount
stakingAmt := stakingCoin.Amount.ToDec()
if bTokenTotalSupply.IsPositive() {
mintAmt = bTokenTotalSupply.Amount.ToDec().Mul(stakingAmt).QuoTruncate(netAmount).TruncateInt()
mintAmt = bTokenTotalSupply.Amount.ToDec().Mul(stakingCoin.Amount.ToDec()).QuoTruncate(netAmount).TruncateInt()
}

// mint on module acc and send
Expand All @@ -90,33 +123,7 @@ func (k Keeper) LiquidStaking(
return sdk.ZeroDec(), err
}

activeVals, totalWeight := k.GetActiveLiquidValidators(ctx)
lenActiveVals := len(activeVals)
share := stakingAmt.QuoTruncate(totalWeight).TruncateInt()
totalNewShares := sdk.ZeroDec()
var weightedShare sdk.Int
for i, val := range activeVals {
if i+1 == lenActiveVals {
// To minimize the decimal error, use the remaining amount for the last validator.
weightedShare = stakingAmt.Sub(totalNewShares).TruncateInt()
} else {
weightedShare = share.ToDec().Mul(val.Weight).TruncateInt()
}
// NOTE: source funds are always unbonded
validator, found := k.stakingKeeper.GetValidator(ctx, val.GetOperator())
if !found {
panic("validator not founded")
}
newShares, err = k.stakingKeeper.Delegate(ctx, proxyAcc, weightedShare, stakingtypes.Unbonded, validator, true)
if err != nil {
return sdk.ZeroDec(), err
}
val.LiquidTokens = val.LiquidTokens.Add(weightedShare)
k.SetLiquidValidator(ctx, val)
totalNewShares = totalNewShares.Add(newShares)
}

return totalNewShares, nil
return k.LiquidDelegate(ctx, proxyAcc, activeVals, stakingCoin.Amount)
}

// LiquidUnstaking ...
Expand All @@ -133,6 +140,11 @@ func (k Keeper) LiquidUnstaking(
)
}

activeVals := k.GetActiveLiquidValidators(ctx)
if activeVals.Len() == 0 || !activeVals.TotalWeight().IsPositive() {
return time.Time{}, []stakingtypes.UnbondingDelegation{}, fmt.Errorf("there's no active liquid validators")
}

// UnstakeAmount = NetAmount * BTokenAmount/TotalSupply * (1-UnstakeFeeRate), review decimal truncation
bTokenTotalSupply := k.bankKeeper.GetSupply(ctx, liquidBondDenom)
if !bTokenTotalSupply.IsPositive() {
Expand All @@ -151,29 +163,30 @@ func (k Keeper) LiquidUnstaking(
return time.Time{}, []stakingtypes.UnbondingDelegation{}, err
}

activeVals, totalWeight := k.GetActiveLiquidValidators(ctx)
lenActiveVals := len(activeVals)
share := unstakeAmount.QuoTruncate(totalWeight).TruncateInt()
share := unstakeAmount.QuoInt(activeVals.TotalWeight()).TruncateInt()
leftAmount := unstakeAmount.TruncateInt()
var weightedShare sdk.Int

var ubdTime time.Time
var ubds []stakingtypes.UnbondingDelegation
for i, val := range activeVals {
if i+1 == lenActiveVals {
lenActiveVals := activeVals.Len()
for i := lenActiveVals - 1; i >= 0; i-- {
if i == 0 {
// To minimize the decimal error, use the remaining amount for the last validator.
weightedShare = leftAmount
} else {
weightedShare = share.ToDec().Mul(val.Weight).TruncateInt()
weightedShare = share.Mul(activeVals[i].Weight)
}
var ubd stakingtypes.UnbondingDelegation
ubdTime, ubd, err = k.LiquidUnbond(ctx, proxyAcc, liquidStaker, val.GetOperator(), weightedShare.ToDec())
del, found := k.stakingKeeper.GetDelegation(ctx, proxyAcc, activeVals[i].GetOperator())
fmt.Println("[liquid UBD]", weightedShare.String(), del.Shares.String(), found)
ubdTime, ubd, err = k.LiquidUnbond(ctx, proxyAcc, liquidStaker, activeVals[i].GetOperator(), weightedShare.ToDec())
if err != nil {
return time.Time{}, []stakingtypes.UnbondingDelegation{}, err
}
ubds = append(ubds, ubd)
val.LiquidTokens = val.LiquidTokens.Sub(weightedShare)
k.SetLiquidValidator(ctx, val)
activeVals[i].LiquidTokens = activeVals[i].LiquidTokens.Sub(weightedShare)
k.SetLiquidValidator(ctx, activeVals[i])
leftAmount = leftAmount.Sub(weightedShare)
}
return ubdTime, ubds, nil
Expand All @@ -196,7 +209,7 @@ func (k Keeper) LiquidUnbond(
// transfer the validator tokens to the not bonded pool
if validator.IsBonded() {
coins := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), returnAmount))
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, coins); err != nil {
if err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, coins); err != nil {
panic(err)
}
}
Expand All @@ -208,6 +221,24 @@ func (k Keeper) LiquidUnbond(
return completionTime, ubd, nil
}

func (k Keeper) WithdrawLiquidRewards(ctx sdk.Context, proxyAcc sdk.AccAddress) (totalRewards sdk.Int) {
bondDenom := k.stakingKeeper.BondDenom(ctx)
k.stakingKeeper.IterateDelegations(
ctx, proxyAcc,
func(_ int64, del stakingtypes.DelegationI) (stop bool) {
valAddr := del.GetValidatorAddr()
reward, err := k.distrKeeper.WithdrawDelegationRewards(ctx, proxyAcc, valAddr)
if err != nil {
// TODO: tmp panic for debugging
panic(err)
}
totalRewards = totalRewards.Add(reward.AmountOf(bondDenom))
return false
},
)
return totalRewards
}

// GetLiquidValidator get a single liquid validator
func (k Keeper) GetLiquidValidator(ctx sdk.Context, addr sdk.ValAddress) (val types.LiquidValidator, found bool) {
store := ctx.KVStore(k.storeKey)
Expand Down Expand Up @@ -244,22 +275,29 @@ func (k Keeper) GetAllLiquidValidators(ctx sdk.Context) (vals []types.LiquidVali
}

// GetActiveLiquidValidators get the set of active liquid validators.
func (k Keeper) GetActiveLiquidValidators(ctx sdk.Context) (vals []types.LiquidValidator, totalWeight sdk.Dec) {
// TODO: refactor []types.LiquidValidator for types.LiquidValidators for totalWeights and len and minMaxGap
func (k Keeper) GetActiveLiquidValidators(ctx sdk.Context) (vals types.LiquidValidators) {
store := ctx.KVStore(k.storeKey)

iterator := sdk.KVStorePrefixIterator(store, types.LiquidValidatorsKey)
totalWeight = sdk.ZeroDec()
//totalWeight = sdk.ZeroInt()
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
val := types.MustUnmarshalLiquidValidator(k.cdc, iterator.Value())
if val.Status == types.ValidatorStatusActive {
vals = append(vals, val)
totalWeight = totalWeight.Add(val.Weight)
//totalWeight = totalWeight.Add(val.Weight)
}
}

return vals, totalWeight
////lenVals = len(vals)
//if lenVals == 0 || !totalWeight.IsPositive() {
// // TODO: make a error type for this
// err = fmt.Errorf("there's no active liquid validators")
//}

return vals
}

// GetAllLiquidValidatorsMap get the set of all liquid validators as map with no limits
Expand Down
Loading

0 comments on commit f6320ec

Please sign in to comment.