Skip to content

Commit

Permalink
feat: React to LSM caps being reached (#632)
Browse files Browse the repository at this point in the history
* lsm cap host chain params

* tests

* dep

* update validator key

* delegable flag

* add lsm flag check

* delegation strategy update

* delegable flag typo

* test updates

* fix test

* changelog

* migration

* handle bond factor default value

* cosmos-sdk dep

* comparison typo

* Update proto/pstake/liquidstakeibc/v1beta1/liquidstakeibc.proto

Co-authored-by: Puneet <59960662+puneet2019@users.noreply.github.com>

* Update proto/pstake/liquidstakeibc/v1beta1/liquidstakeibc.proto

Co-authored-by: Puneet <59960662+puneet2019@users.noreply.github.com>

* Update proto/pstake/liquidstakeibc/v1beta1/liquidstakeibc.proto

Co-authored-by: Puneet <59960662+puneet2019@users.noreply.github.com>

* add explanation comment to delegation strategy

* bond factor validation

---------

Co-authored-by: Puneet <59960662+puneet2019@users.noreply.github.com>
  • Loading branch information
kruspy and puneet2019 authored Sep 6, 2023
1 parent ba890c7 commit ba52d5c
Show file tree
Hide file tree
Showing 17 changed files with 766 additions and 164 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Bug Fixes

- [#632](https://github.com/persistenceOne/pstake-native/pull/632) LSM cap fix.
- [#621](https://github.com/persistenceOne/pstake-native/pull/621) ICA recreation timeout fix.

### Removed
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand Down Expand Up @@ -167,6 +166,7 @@ require (
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.6 // indirect
pgregory.net/rapid v0.5.5 // indirect
Expand All @@ -182,6 +182,6 @@ replace (

// use persistence's forks with LSM implemented
replace (
github.com/cosmos/cosmos-sdk => github.com/persistenceOne/cosmos-sdk v0.47.3-lsm3
github.com/cosmos/cosmos-sdk => github.com/persistenceOne/cosmos-sdk v0.47.3-lsm4
github.com/cosmos/ibc-go/v7 => github.com/persistenceOne/ibc-go/v7 v7.2.0-lsm2
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/persistenceOne/cosmos-sdk v0.47.3-lsm3 h1:lfX+OnKmmbJkijtIUu74C4BevzFXXjEGlxPLb6G6ngc=
github.com/persistenceOne/cosmos-sdk v0.47.3-lsm3/go.mod h1:4oxikyyHyEe1wlYQFMGITfW/r01wYtfj8yjwru7bSWE=
github.com/persistenceOne/cosmos-sdk v0.47.3-lsm4 h1:HyoRiPw30IOFdNyhrXFOrU20fR1UKgtv7PsTBeVpsMU=
github.com/persistenceOne/cosmos-sdk v0.47.3-lsm4/go.mod h1:4oxikyyHyEe1wlYQFMGITfW/r01wYtfj8yjwru7bSWE=
github.com/persistenceOne/ibc-go/v7 v7.2.0-lsm2 h1:dZdIkhaOKB0YbetYmlbQmSC6b8hSPYUzOeET9CIYUDY=
github.com/persistenceOne/ibc-go/v7 v7.2.0-lsm2/go.mod h1:PzDs4fL8PNx7OoxTbNqjxANKKNGL/1ucVt+Np68hLi4=
github.com/persistenceOne/persistence-sdk/v2 v2.1.1 h1:fo8Og2QkjsqqhH/wiEiOSB99M2Jr0kLqirU2xhJRZfQ=
Expand Down
16 changes: 16 additions & 0 deletions proto/pstake/liquidstakeibc/v1beta1/liquidstakeibc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ message HostChainLSParams {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
]; // protocol fee in percentage
// LSM validator cap
// Should be used only when HostChainFlag.Lsm == true, orelse default
string lsm_validator_cap = 6 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// LSM bond factor
// Should be used only when HostChainFlag.Lsm == true, orelse default
string lsm_bond_factor = 7 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

message ICAAccount {
Expand Down Expand Up @@ -133,6 +147,8 @@ message Validator {
];
// the unbonding epoch number when the validator transitioned into the state
int64 unbonding_epoch = 6;
// whether the validator can accept delegations or not, default true for non-lsm chains
bool delegable = 7;
}

message Deposit {
Expand Down
10 changes: 6 additions & 4 deletions x/liquidstakeibc/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ func TestGenesis(t *testing.T) {
ChainId: "chainA-1",
ConnectionId: "connection-1",
Params: &types.HostChainLSParams{
DepositFee: sdk.ZeroDec(),
RestakeFee: sdk.ZeroDec(),
UnstakeFee: sdk.ZeroDec(),
RedemptionFee: sdk.ZeroDec(),
DepositFee: sdk.ZeroDec(),
RestakeFee: sdk.ZeroDec(),
UnstakeFee: sdk.ZeroDec(),
RedemptionFee: sdk.ZeroDec(),
LsmValidatorCap: sdk.NewDec(1),
LsmBondFactor: sdk.NewDec(-1),
},
HostDenom: "uatom",
ChannelId: "channel-1",
Expand Down
58 changes: 43 additions & 15 deletions x/liquidstakeibc/keeper/delegation_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,60 @@ import (

type DelegateAmount struct {
ValAddress string
ValWeight sdk.Dec
Amount sdk.Dec
}

// GenerateDelegateMessages produces the same result regardless the LSM flag on the host chain.
func (k *Keeper) GenerateDelegateMessages(hc *types.HostChain, depositAmount math.Int) ([]proto.Message, error) {
return k.generateMessages(hc, depositAmount, false)
// filter out validators which are non-delegable (which reached any LSM cap)
delegableValidators := make([]*types.Validator, 0)
nonDelegableWeight := sdk.ZeroDec()
nonDelegableDelegations := sdk.ZeroInt()
for _, validator := range hc.Validators {
if validator.Delegable {
delegableValidators = append(delegableValidators, validator)
} else {
nonDelegableWeight = nonDelegableWeight.Add(validator.Weight)
nonDelegableDelegations = nonDelegableDelegations.Add(validator.DelegatedAmount)
}
}

// if there are no delegable validators, do nothing
if len(delegableValidators) == 0 {
return nil, errorsmod.Wrap(types.ErrInvalidMessages, "no delegable validators")
}

// the weight of the un-delegable validators is distributed evenly among the others
if nonDelegableWeight.GT(sdk.ZeroDec()) {
weightDelta := nonDelegableWeight.Quo(sdk.NewDec(int64(len(delegableValidators))))
for _, validator := range delegableValidators {
validator.Weight = validator.Weight.Add(weightDelta)
}
}

// subtract the delegations from non-delegable validators to get the effective total delegated amount
effectiveTotalDelegatedAmount := hc.GetHostChainTotalDelegations().Sub(nonDelegableDelegations)

return k.generateMessages(hc, delegableValidators, effectiveTotalDelegatedAmount, depositAmount, false)
}

func (k *Keeper) GenerateUndelegateMessages(hc *types.HostChain, unbondAmount math.Int) ([]proto.Message, error) {
return k.generateMessages(hc, unbondAmount, true)
return k.generateMessages(hc, hc.Validators, hc.GetHostChainTotalDelegations(), unbondAmount, true)
}

func (k *Keeper) generateMessages(
hc *types.HostChain,
validators []*types.Validator,
totalDelegatedAmount math.Int,
actionableAmount math.Int,
undelegating bool,
) ([]proto.Message, error) {
delegateAmounts := make([]DelegateAmount, 0)
for _, validator := range hc.Validators {
for _, validator := range validators {
// calculate the new total delegated amount for the host chain
currentDelegation := hc.GetHostChainTotalDelegations()
futureDelegation := currentDelegation.Add(actionableAmount)
futureDelegation := totalDelegatedAmount.Add(actionableAmount)
if undelegating {
futureDelegation = currentDelegation.Sub(actionableAmount)
}

if validator.Weight.Equal(sdk.ZeroDec()) {
continue // skip validators with zero weight
futureDelegation = totalDelegatedAmount.Sub(actionableAmount)
}

// calculate the delegated/undelegated amount difference for the validator:
Expand All @@ -56,13 +82,14 @@ func (k *Keeper) generateMessages(

delegateAmounts = append(delegateAmounts, DelegateAmount{
ValAddress: validator.OperatorAddress,
ValWeight: validator.Weight,
Amount: newDelegationDifference,
})
}

messages := make([]proto.Message, 0)
for _, delegationAmount := range delegateAmounts {
for i, delegationAmount := range delegateAmounts {
// create the basic structure of the delegate / undelegate message
// containing both the delegator and validator addresses
var message proto.Message
if !undelegating {
message = &stakingtypes.MsgDelegate{
Expand All @@ -76,8 +103,9 @@ func (k *Keeper) generateMessages(
}
}

// return when there is nothing more to delegate/undelegate
if actionableAmount.LTE(delegationAmount.Amount.TruncateInt()) {
// if what's left to delegate is less than what needs to be delegated OR we are in the last validator just delegate everything that is left
// this will also remove any remainder tokens that can be left because of precision issues
if actionableAmount.LTE(delegationAmount.Amount.TruncateInt()) || i == len(delegateAmounts)-1 {
if !undelegating {
msgDelegate := message.(*stakingtypes.MsgDelegate)
msgDelegate.Amount = sdk.NewCoin(hc.HostDenom, actionableAmount)
Expand Down
Loading

0 comments on commit ba52d5c

Please sign in to comment.