Skip to content

Commit

Permalink
Merge pull request #342 from cosmosquad-labs/fix-voting-power-endpoin…
Browse files Browse the repository at this point in the history
…t-calculation-bug

fix: calculation bug of liquidstaking voting_power endpoint
  • Loading branch information
dongsam authored Jul 27, 2022
2 parents 197de6e + a048288 commit d9f3880
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 7 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

* (x/liquidity) [\#341](https://github.com/cosmosquad-labs/squad/pull/341) Enable detailed configuration for order book responses

## v2.1.0
### Bug Fixes
* (x/liquidstaking) [\#342](https://github.com/cosmosquad-labs/squad/pull/342) fix: calculation bug of liquidstaking voting_power endpoint

## [v2.1.0] - 2022-07-18

### Client Breaking Changes

Expand Down Expand Up @@ -133,6 +136,7 @@ Running a full node will encounter wrong app hash issue if it doesn't upgrade to
* `x/vesting` feat: periodic vesting msg
* `x/bank` feat: Add dynamic blockedAddrs

[Unreleased]: https://github.com/cosmosquad-labs/squad/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/cosmosquad-labs/squad/compare/v2.1.0...HEAD
[v1.0.0]: https://github.com/cosmosquad-labs/squad/releases/tag/v1.0.0
[v1.1.0]: https://github.com/cosmosquad-labs/squad/releases/tag/v1.1.0
[v1.1.0]: https://github.com/cosmosquad-labs/squad/releases/tag/v1.1.0
[v2.1.0]: https://github.com/cosmosquad-labs/squad/releases/tag/v2.1.0
7 changes: 7 additions & 0 deletions x/liquidstaking/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,10 @@ func (s *KeeperTestSuite) Unstake(farmerAcc sdk.AccAddress, amt sdk.Coins) {
err := s.app.FarmingKeeper.Unstake(s.ctx, farmerAcc, amt)
s.Require().NoError(err)
}

func (s *KeeperTestSuite) assertVotingPower(addr sdk.AccAddress, stakingVotingPower, liquidStakingVotingPower, validatorVotingPower sdk.Int) {
vp := s.keeper.GetVotingPower(s.ctx, addr)
s.Require().Equal(stakingVotingPower, vp.StakingVotingPower)
s.Require().Equal(liquidStakingVotingPower, vp.LiquidStakingVotingPower)
s.Require().Equal(validatorVotingPower, vp.ValidatorVotingPower)
}
20 changes: 16 additions & 4 deletions x/liquidstaking/keeper/tally.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ func (k Keeper) GetVoterBalanceByDenom(ctx sdk.Context, votes govtypes.Votes) ma
return denomAddrBalanceMap
}

// GetBTokenSharePerPoolCoinMap creates bTokenSharePerPoolCoinMap of pool coins which is containing target denom to calculate target denom value of farming positions
func (k Keeper) GetBTokenSharePerPoolCoinMap(ctx sdk.Context, targetDenom string) map[string]sdk.Dec {
bTokenSharePerPoolCoinMap := map[string]sdk.Dec{}
_ = k.liquidityKeeper.IterateAllPools(ctx, func(pool liquiditytypes.Pool) (stop bool, err error) {
bTokenSharePerPoolCoin := k.TokenSharePerPoolCoin(ctx, targetDenom, pool.PoolCoinDenom)
if bTokenSharePerPoolCoin.IsPositive() {
bTokenSharePerPoolCoinMap[pool.PoolCoinDenom] = bTokenSharePerPoolCoin
}
return false, nil
})
return bTokenSharePerPoolCoinMap
}

// TokenAmountFromFarmingPositions returns worth of staked tokens amount of exist farming positions including queued of the addr
func (k Keeper) TokenAmountFromFarmingPositions(ctx sdk.Context, addr sdk.AccAddress, targetDenom string, tokenSharePerPoolCoinMap map[string]sdk.Dec) sdk.Int {
tokenAmount := sdk.ZeroInt()
Expand Down Expand Up @@ -158,7 +171,8 @@ func (k Keeper) CalcLiquidStakingVotingPower(ctx sdk.Context, addr sdk.AccAddres
}

bTokenAmount := sdk.ZeroInt()
bTokenSharePerPoolCoinMap := map[string]sdk.Dec{}
bTokenSharePerPoolCoinMap := k.GetBTokenSharePerPoolCoinMap(ctx, liquidBondDenom)

balances := k.bankKeeper.SpendableCoins(ctx, addr)
for _, coin := range balances {
// add balance of bToken
Expand All @@ -167,9 +181,7 @@ func (k Keeper) CalcLiquidStakingVotingPower(ctx sdk.Context, addr sdk.AccAddres
}

// check if the denom is pool coin
bTokenSharePerPoolCoin := k.TokenSharePerPoolCoin(ctx, liquidBondDenom, coin.Denom)
if bTokenSharePerPoolCoin.IsPositive() {
bTokenSharePerPoolCoinMap[coin.Denom] = bTokenSharePerPoolCoin
if bTokenSharePerPoolCoin, ok := bTokenSharePerPoolCoinMap[coin.Denom]; ok && bTokenSharePerPoolCoin.IsPositive() {
bTokenAmount = bTokenAmount.Add(utils.GetShareValue(coin.Amount, bTokenSharePerPoolCoin))
}
}
Expand Down
134 changes: 134 additions & 0 deletions x/liquidstaking/keeper/tally_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,140 @@ func (s *KeeperTestSuite) TestSetLiquidStakingVotingPowers() {
setLiquidStakingVotingPowers()
}

// test Liquid Staking gov voting power for the address
func (s *KeeperTestSuite) TestGetVotingPower() {
params := types.DefaultParams()
liquidBondDenom := s.keeper.LiquidBondDenom(s.ctx)

// v1, v2, v3, v4
vals, valOpers, _ := s.CreateValidators([]int64{10000000, 10000000, 10000000, 10000000, 10000000})
params.WhitelistedValidators = []types.WhitelistedValidator{
{ValidatorAddress: valOpers[0].String(), TargetWeight: sdk.NewInt(10)},
{ValidatorAddress: valOpers[1].String(), TargetWeight: sdk.NewInt(10)},
{ValidatorAddress: valOpers[2].String(), TargetWeight: sdk.NewInt(10)},
{ValidatorAddress: valOpers[3].String(), TargetWeight: sdk.NewInt(10)},
}
s.keeper.SetParams(s.ctx, params)
s.keeper.UpdateLiquidValidatorSet(s.ctx)

val4, _ := s.app.StakingKeeper.GetValidator(s.ctx, valOpers[3])

delA := s.addrs[0] // zero power case
delB := s.addrs[1] // Balance of bToken case
delC := s.addrs[2] // Balance of PoolCoins including bToken, Farming position of PoolCoins that include bToken
delD := s.addrs[3] // Farming position of bToken case
delE := s.addrs[6] // normal staking case, balance of bToken case

s.assertVotingPower(delA, sdk.ZeroInt(), sdk.ZeroInt(), sdk.ZeroInt())
s.assertVotingPower(delE, sdk.ZeroInt(), sdk.ZeroInt(), sdk.ZeroInt())
s.assertVotingPower(vals[3], sdk.ZeroInt(), sdk.ZeroInt(), sdk.NewInt(10000000))

_, err := s.app.StakingKeeper.Delegate(s.ctx, delE, sdk.NewInt(60000000), stakingtypes.Unbonded, val4, true)
s.Require().NoError(err)

s.assertVotingPower(delE, sdk.NewInt(60000000), sdk.ZeroInt(), sdk.ZeroInt())
s.assertVotingPower(vals[3], sdk.ZeroInt(), sdk.ZeroInt(), sdk.NewInt(70000000))

delBbToken := sdk.NewInt(80000000)
delCbToken := sdk.NewInt(60000000)
delDbToken := sdk.NewInt(20000000)
delEbToken := sdk.NewInt(80000000)

newShares, bToken, err := s.keeper.LiquidStake(s.ctx, types.LiquidStakingProxyAcc, delB, sdk.NewCoin(sdk.DefaultBondDenom, delBbToken))
s.Require().NoError(err)
s.Require().EqualValues(newShares.TruncateInt(), bToken, s.app.BankKeeper.GetBalance(s.ctx, delB, liquidBondDenom).Amount, delBbToken)
s.assertVotingPower(delB, sdk.ZeroInt(), delBbToken, sdk.ZeroInt())

newShares, bToken, err = s.keeper.LiquidStake(s.ctx, types.LiquidStakingProxyAcc, delC, sdk.NewCoin(sdk.DefaultBondDenom, delCbToken))
s.Require().NoError(err)
s.Require().EqualValues(newShares.TruncateInt(), bToken, s.app.BankKeeper.GetBalance(s.ctx, delC, liquidBondDenom).Amount, delCbToken)
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

newShares, bToken, err = s.keeper.LiquidStake(s.ctx, types.LiquidStakingProxyAcc, delD, sdk.NewCoin(sdk.DefaultBondDenom, delDbToken))
s.Require().NoError(err)
s.Require().EqualValues(newShares.TruncateInt(), bToken, s.app.BankKeeper.GetBalance(s.ctx, delD, liquidBondDenom).Amount, delDbToken)
s.assertVotingPower(delD, sdk.ZeroInt(), delDbToken, sdk.ZeroInt())

newShares, bToken, err = s.keeper.LiquidStake(s.ctx, types.LiquidStakingProxyAcc, delE, sdk.NewCoin(sdk.DefaultBondDenom, delEbToken))
s.Require().NoError(err)
s.Require().EqualValues(newShares.TruncateInt(), bToken, s.app.BankKeeper.GetBalance(s.ctx, delE, liquidBondDenom).Amount, delEbToken)
s.assertVotingPower(delE, sdk.NewInt(60000000), delEbToken, sdk.ZeroInt())

s.assertVotingPower(vals[3], sdk.ZeroInt(), sdk.ZeroInt(), sdk.NewInt(130000000)) // self bonding 10000000 + normal staking 60000000 + liquid staking 240000000/4

// Test balance of PoolTokens including bToken
pair1 := s.createPair(delB, params.LiquidBondDenom, sdk.DefaultBondDenom, false)
pool1 := s.createPool(delB, pair1.Id, sdk.NewCoins(sdk.NewCoin(params.LiquidBondDenom, sdk.NewInt(40000000)), sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(44000000))), false)
s.assertVotingPower(delB, sdk.ZeroInt(), delBbToken, sdk.ZeroInt())

pair2 := s.createPair(delC, sdk.DefaultBondDenom, params.LiquidBondDenom, false)
pool2 := s.createPool(delC, pair2.Id, sdk.NewCoins(sdk.NewCoin(params.LiquidBondDenom, sdk.NewInt(60000000)), sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(44000000))), false)
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

// Test Farming Queued Staking of bToken
s.CreateFixedAmountPlan(s.addrs[0], map[string]string{params.LiquidBondDenom: "0.4", pool1.PoolCoinDenom: "0.3", pool2.PoolCoinDenom: "0.3"}, map[string]int64{"stake": 1})
s.Stake(delD, sdk.NewCoins(sdk.NewCoin(params.LiquidBondDenom, sdk.NewInt(10000000))))
queuedStakingAmt := s.app.FarmingKeeper.GetAllQueuedStakingAmountByFarmerAndDenom(s.ctx, delD, params.LiquidBondDenom)
s.Equal(queuedStakingAmt, sdk.NewInt(10000000))
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

// Test Farming Staking Position Staking of bToken without balance
s.advanceEpochDays()
staking, found := s.app.FarmingKeeper.GetStaking(s.ctx, params.LiquidBondDenom, delD)
s.True(found)
s.Equal(staking.Amount, sdk.NewInt(10000000))
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

// Test Farming Queued Staking of bToken
s.CreateFixedAmountPlan(s.addrs[0], map[string]string{params.LiquidBondDenom: "0.4", pool1.PoolCoinDenom: "0.3", pool2.PoolCoinDenom: "0.3"}, map[string]int64{"stake": 1})
s.Stake(delD, sdk.NewCoins(sdk.NewCoin(params.LiquidBondDenom, sdk.NewInt(10000000))))
queuedStakingAmt = s.app.FarmingKeeper.GetAllQueuedStakingAmountByFarmerAndDenom(s.ctx, delD, params.LiquidBondDenom)
s.Equal(queuedStakingAmt, sdk.NewInt(10000000))
s.assertVotingPower(delD, sdk.ZeroInt(), delDbToken, sdk.ZeroInt())

// Test Farming Staking Position Staking of bToken without balance
s.advanceEpochDays()
staking, found = s.app.FarmingKeeper.GetStaking(s.ctx, params.LiquidBondDenom, delD)
s.True(found)
s.Equal(staking.Amount, sdk.NewInt(20000000))
s.assertVotingPower(delD, sdk.ZeroInt(), delDbToken, sdk.ZeroInt())

// Test Farming Queued Staking of PoolTokens including bToken
s.Stake(delC, sdk.NewCoins(sdk.NewCoin(pool2.PoolCoinDenom, sdk.NewInt(500000000000))))
queuedStakingAmt = s.app.FarmingKeeper.GetAllQueuedStakingAmountByFarmerAndDenom(s.ctx, delC, pool2.PoolCoinDenom)
s.Equal(queuedStakingAmt, sdk.NewInt(500000000000))
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

// Test Farming Staking Position of PoolTokens including bToken
s.advanceEpochDays()
staking, found = s.app.FarmingKeeper.GetStaking(s.ctx, pool2.PoolCoinDenom, delC)
s.True(found)
s.Equal(staking.Amount, sdk.NewInt(500000000000))
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

// Test Farming Queued Staking of PoolTokens including bToken without balance
s.Stake(delC, sdk.NewCoins(sdk.NewCoin(pool2.PoolCoinDenom, sdk.NewInt(500000000000))))
queuedStakingAmt = s.app.FarmingKeeper.GetAllQueuedStakingAmountByFarmerAndDenom(s.ctx, delC, pool2.PoolCoinDenom)
s.Equal(queuedStakingAmt, sdk.NewInt(500000000000))
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

// Test Farming Staking Position of PoolTokens including bToken
s.advanceEpochDays()
staking, found = s.app.FarmingKeeper.GetStaking(s.ctx, pool2.PoolCoinDenom, delC)
s.True(found)
s.Equal(staking.Amount, sdk.NewInt(1000000000000))
s.assertVotingPower(delC, sdk.ZeroInt(), delCbToken, sdk.ZeroInt())

s.assertVotingPower(delA, sdk.ZeroInt(), sdk.ZeroInt(), sdk.ZeroInt())
s.assertVotingPower(delE, sdk.NewInt(60000000), delEbToken, sdk.ZeroInt())

// send bToken of E to vals[3]
err = s.app.BankKeeper.SendCoins(s.ctx, delE, vals[3], sdk.NewCoins(sdk.NewCoin(liquidBondDenom, delEbToken)))
s.Require().NoError(err)
s.assertVotingPower(delE, sdk.NewInt(60000000), sdk.ZeroInt(), sdk.ZeroInt())
s.assertVotingPower(vals[3], sdk.ZeroInt(), delEbToken, sdk.NewInt(130000000)) // self bonding 10000000 + normal staking 60000000 + liquid staking 240000000/4
}

// test Liquid Staking gov power
func (s *KeeperTestSuite) TestTallyLiquidStakingGov2() {
params := types.DefaultParams()
Expand Down
1 change: 1 addition & 0 deletions x/liquidstaking/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type LiquidityKeeper interface {
GetPool(ctx sdk.Context, id uint64) (pool liquiditytypes.Pool, found bool)
GetPoolBalances(ctx sdk.Context, pool liquiditytypes.Pool) (rx sdk.Coin, ry sdk.Coin)
GetPoolCoinSupply(ctx sdk.Context, pool liquiditytypes.Pool) sdk.Int
IterateAllPools(ctx sdk.Context, cb func(pool liquiditytypes.Pool) (stop bool, err error)) error
}

// FarmingKeeper expected farming keeper (noalias)
Expand Down

0 comments on commit d9f3880

Please sign in to comment.