Skip to content

Commit

Permalink
superfluid delegation queries (#915)
Browse files Browse the repository at this point in the history
* base

* remove duplicate import

* add delegator addresses to test scaffolding

* add delegation amounts to test scaffolding

* queries

* base tests pass

* test unbonding amounts are not included

* fix merge conflicts

* fix naming of function from suffix to syntheticdenom

* rename SuperfluidDelegatedAmountByValidatorDenom to EstimateSuperfluidDelegatedAmountByValidatorDenom

* remove total delegated from SuperfluidDelegationsByValidatorDenom

* fix comments
  • Loading branch information
sunnya97 authored Feb 21, 2022
1 parent 6d513c1 commit 5be21bb
Show file tree
Hide file tree
Showing 22 changed files with 3,582 additions and 512 deletions.
55 changes: 55 additions & 0 deletions go.sum

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions proto/osmosis/superfluid/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,56 @@ service Query {
option (google.api.http).get =
"/osmosis/superfluid/v1beta1/connected_intermediary_account/{lock_id}";
}

// Returns the coins superfluid delegated for a delegator, validator, denom
// triplet
rpc SuperfluidDelegationAmount(SuperfluidDelegationAmountRequest)
returns (SuperfluidDelegationAmountResponse) {
option (google.api.http).get =
"/osmosis/superfluid/v1beta1/superfluid_delegation_amount";
}

// Returns all the superfluid poistions for a specific delegator
rpc SuperfluidDelegationsByDelegator(SuperfluidDelegationsByDelegatorRequest)
returns (SuperfluidDelegationsByDelegatorResponse) {
option (google.api.http).get = "/osmosis/superfluid/v1beta1/"
"superfluid_delegations/{delegator_address}";
}

// Returns all the superfluid positions of a specific denom delegated to one
// validator
rpc SuperfluidDelegationsByValidatorDenom(
SuperfluidDelegationsByValidatorDenomRequest)
returns (SuperfluidDelegationsByValidatorDenomResponse) {
option (google.api.http).get =
"/osmosis/superfluid/v1beta1/superfluid_delegations_by_validator_denom";
}

// Returns the amount of a specific denom delegated to a specific validator
// This is labeled an estimate, because the way it calculates the amount can
// lead rounding errors from the true delegated amount
rpc EstimateSuperfluidDelegatedAmountByValidatorDenom(
EstimateSuperfluidDelegatedAmountByValidatorDenomRequest)
returns (EstimateSuperfluidDelegatedAmountByValidatorDenomResponse) {
option (google.api.http).get =
"/osmosis/superfluid/v1beta1/"
"estimate_superfluid_delegation_amount_by_validator_denom";
}

// // Returns all the unbonding superfluid positions of a delegator
// rpc SuperfluidUnbondingsByDelegator(SuperfluidUnbondingsByDelegatorRequest)
// returns (SuperfluidUnbondingsByDelegatorResponse) {
// option (google.api.http).get =
// "/osmosis/superfluid/v1beta1/superfluid_unbondings/{delegator_address}";
// }

// // Returns all the unbonding superfluid positions of a specific denom
// unbonding from one validator rpc
// SuperfluidUnbondingsByValidatorDenom(SuperfluidUnbondingsByValidatorDenomRequest)
// returns (SuperfluidUnbondingsByValidatorDenomResponse) {
// option (google.api.http).get =
// "/osmosis/superfluid/v1beta1/superfluid_unbondings_by_validator_denom";
// }
}

message AssetTypeRequest { string denom = 1; };
Expand Down Expand Up @@ -74,3 +124,78 @@ message ConnectedIntermediaryAccountRequest { uint64 lock_id = 1; }
message ConnectedIntermediaryAccountResponse {
SuperfluidIntermediaryAccountInfo account = 1;
}

message SuperfluidDelegationAmountRequest {
string delegator_address = 1;
string validator_address = 2;
string denom = 3;
}

message SuperfluidDelegationAmountResponse {
repeated cosmos.base.v1beta1.Coin amount = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

message SuperfluidDelegationsByDelegatorRequest {
string delegator_address = 1;
}

message SuperfluidDelegationsByDelegatorResponse {
repeated SuperfluidDelegationRecord superfluid_delegation_records = 1
[ (gogoproto.nullable) = false ];
repeated cosmos.base.v1beta1.Coin total_delegated_coins = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

message SuperfluidDelegationsByValidatorDenomRequest {
string validator_address = 1;
string denom = 2;
}

message SuperfluidDelegationsByValidatorDenomResponse {
repeated SuperfluidDelegationRecord superfluid_delegation_records = 1
[ (gogoproto.nullable) = false ];
}

message EstimateSuperfluidDelegatedAmountByValidatorDenomRequest {
string validator_address = 1;
string denom = 2;
}

message EstimateSuperfluidDelegatedAmountByValidatorDenomResponse {
repeated cosmos.base.v1beta1.Coin total_delegated_coins = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// message SuperfluidUnbondingsByDelegatorRequest {
// string delegator_address = 1;
// }

// message SuperfluidUnbondingsByDelegatorResponse {
// repeated SuperfluidDelegationRecord superfluid_unbonding_records = 1
// [ (gogoproto.nullable) = false ];
// repeated cosmos.base.v1beta1.Coin total_unbonding_coins = 2 [
// (gogoproto.nullable) = false,
// (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
// ];
// }

// message SuperfluidUnbondingsByValidatorDenomRequest {
// string validator_address = 1;
// string denom = 2;
// }

// message SuperfluidUnbondingsByValidatorDenomResponse {
// repeated SuperfluidDelegationRecord superfluid_unbonding_records = 1
// [ (gogoproto.nullable) = false ];
// repeated cosmos.base.v1beta1.Coin total_unbonding_coins = 2 [
// (gogoproto.nullable) = false,
// (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
// ];
// }
11 changes: 11 additions & 0 deletions proto/osmosis/superfluid/superfluid.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,14 @@ message OsmoEquivalentMultiplierRecord {
(gogoproto.nullable) = false
];
}

// SuperfluidDelegationRecord takes the role of intermediary between LP token
// and OSMO tokens for superfluid staking
message SuperfluidDelegationRecord {
string delegator_address = 1;
string validator_address = 2;
cosmos.base.v1beta1.Coin delegation_amount = 3 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coin"
];
}
5 changes: 5 additions & 0 deletions x/lockup/keeper/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ func (k Keeper) GetAccountLockedLongerDurationDenom(ctx sdk.Context, addr sdk.Ac
return combineLocks(notUnlockings, unlockings)
}

// GetAccountLockedLongerDurationDenom Returns account locked with duration longer than specified with specific denom
func (k Keeper) GetAccountLockedLongerDurationDenomNotUnlockingOnly(ctx sdk.Context, addr sdk.AccAddress, denom string, duration time.Duration) []types.PeriodLock {
return k.getLocksFromIterator(ctx, k.AccountLockIteratorLongerDurationDenom(ctx, false, addr, denom, duration))
}

// GetLocksPastTimeDenom Returns the locks whose unlock time is beyond timestamp
func (k Keeper) GetLocksPastTimeDenom(ctx sdk.Context, denom string, timestamp time.Time) []types.PeriodLock {
// returns both unlocking started and not started assuming it started unlocking current time
Expand Down
15 changes: 9 additions & 6 deletions x/superfluid/keeper/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,28 @@ func (suite *KeeperTestSuite) TestMoveSuperfluidDelegationRewardToGauges() {
{
"happy path with single validator and delegator",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, "gamm/pool/1"}},
[]superfluidDelegation{{0, 0, "gamm/pool/1", 1000000}},
[]int64{0},
[]gaugeChecker{{1, 0, "gamm/pool/1", true}},
},
{
"two LP tokens delegation to a single validator",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, "gamm/pool/1"}, {0, "gamm/pool/2"}},
[]superfluidDelegation{{0, 0, "gamm/pool/1", 1000000}, {0, 0, "gamm/pool/2", 1000000}},
[]int64{0},
[]gaugeChecker{{1, 0, "gamm/pool/1", true}, {2, 0, "gamm/pool/2", true}},
},
{
"one LP token with two locks to a single validator",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, "gamm/pool/1"}, {0, "gamm/pool/1"}},
[]superfluidDelegation{{0, 0, "gamm/pool/1", 1000000}, {0, 0, "gamm/pool/1", 1000000}},
[]int64{0},
[]gaugeChecker{{1, 0, "gamm/pool/1", true}},
},
{
"add unbonded validator case",
[]stakingtypes.BondStatus{stakingtypes.Bonded, stakingtypes.Unbonded},
[]superfluidDelegation{{0, "gamm/pool/1"}, {1, "gamm/pool/1"}},
[]superfluidDelegation{{0, 0, "gamm/pool/1", 1000000}, {0, 1, "gamm/pool/1", 1000000}},
[]int64{0},
[]gaugeChecker{{1, 0, "gamm/pool/1", true}, {2, 1, "gamm/pool/1", false}},
},
Expand All @@ -75,11 +75,14 @@ func (suite *KeeperTestSuite) TestMoveSuperfluidDelegationRewardToGauges() {
suite.Run(tc.name, func() {
suite.SetupTest()

// Generate delegator addresses
delAddrs := CreateRandomAccounts(1)

// setup validators
valAddrs := suite.SetupValidators(tc.validatorStats)

// setup superfluid delegations
suite.SetupSuperfluidDelegations(valAddrs, tc.superDelegations)
suite.SetupSuperfluidDelegations(delAddrs, valAddrs, tc.superDelegations)
unbondingDuration := suite.app.StakingKeeper.GetParams(suite.ctx).UnbondingTime

// allocate rewards to first validator
Expand All @@ -98,7 +101,7 @@ func (suite *KeeperTestSuite) TestMoveSuperfluidDelegationRewardToGauges() {
suite.Require().Equal(gauge.IsPerpetual, true)
suite.Require().Equal(gauge.DistributeTo, lockuptypes.QueryCondition{
LockQueryType: lockuptypes.ByDuration,
Denom: keeper.StakingSuffix(gaugeCheck.lpDenom, valAddrs[gaugeCheck.valIndex].String()),
Denom: keeper.StakingSyntheticDenom(gaugeCheck.lpDenom, valAddrs[gaugeCheck.valIndex].String()),
Duration: unbondingDuration,
})
if gaugeCheck.rewarded {
Expand Down
4 changes: 2 additions & 2 deletions x/superfluid/keeper/export_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package keeper

var StakingSuffix = stakingSuffix
var UnstakingSuffix = unstakingSuffix
var StakingSyntheticDenom = stakingSyntheticDenom
var UnstakingSyntheticDenom = unstakingSyntheticDenom
153 changes: 153 additions & 0 deletions x/superfluid/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package keeper

import (
"context"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/osmosis-labs/osmosis/v7/x/superfluid/types"
)

Expand Down Expand Up @@ -75,3 +78,153 @@ func (k Keeper) ConnectedIntermediaryAccount(goCtx context.Context, req *types.C
},
}, nil
}

// SuperfluidDelegationAmount returns the coins superfluid delegated for a
//delegator, validator, denom triplet
func (k Keeper) SuperfluidDelegationAmount(goCtx context.Context, req *types.SuperfluidDelegationAmountRequest) (*types.SuperfluidDelegationAmountResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetSuperfluidAsset(ctx, req.Denom).Denom == "" {
return nil, types.ErrNonSuperfluidAsset
}

_, err := sdk.ValAddressFromBech32(req.ValidatorAddress)
if err != nil {
return nil, err
}

syntheticDenom := stakingSyntheticDenom(req.Denom, req.ValidatorAddress)

delAddr, err := sdk.AccAddressFromBech32(req.DelegatorAddress)
if err != nil {
return nil, err
}

periodLocks := k.lk.GetAccountLockedLongerDurationDenomNotUnlockingOnly(ctx, delAddr, syntheticDenom, time.Second)

if len(periodLocks) == 0 {
return &types.SuperfluidDelegationAmountResponse{sdk.NewCoins()}, nil
}

return &types.SuperfluidDelegationAmountResponse{periodLocks[0].GetCoins()}, nil
}

// SuperfluidDelegationsByDelegator returns all the superfluid poistions for a specific delegator
func (k Keeper) SuperfluidDelegationsByDelegator(goCtx context.Context, req *types.SuperfluidDelegationsByDelegatorRequest) (*types.SuperfluidDelegationsByDelegatorResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

delAddr, err := sdk.AccAddressFromBech32(req.DelegatorAddress)
if err != nil {
return nil, err
}

res := types.SuperfluidDelegationsByDelegatorResponse{
SuperfluidDelegationRecords: []types.SuperfluidDelegationRecord{},
TotalDelegatedCoins: sdk.NewCoins(),
}

syntheticLocks := k.lk.GetAllSyntheticLockupsByAddr(ctx, delAddr)

for _, syntheticLock := range syntheticLocks {
// don't include unbonding delegations
if strings.Contains(syntheticLock.SynthDenom, "superunbonding") {
continue
}

periodLock, err := k.lk.GetLockByID(ctx, syntheticLock.UnderlyingLockId)
if err != nil {
return nil, err
}

baseDenom := periodLock.Coins.GetDenomByIndex(0)
lockedCoins := sdk.NewCoin(baseDenom, periodLock.GetCoins().AmountOf(baseDenom))
valAddr, err := ValidatorAddressFromSuffix(syntheticLock.SynthDenom)
if err != nil {
return nil, err
}
res.SuperfluidDelegationRecords = append(res.SuperfluidDelegationRecords,
types.SuperfluidDelegationRecord{
DelegatorAddress: req.DelegatorAddress,
ValidatorAddress: valAddr,
DelegationAmount: lockedCoins,
},
)
res.TotalDelegatedCoins = res.TotalDelegatedCoins.Add(lockedCoins)
}
return &res, nil

}

// SuperfluidDelegationsByValidatorDenom returns all the superfluid positions
// of a specific denom delegated to one validator
func (k Keeper) SuperfluidDelegationsByValidatorDenom(goCtx context.Context, req *types.SuperfluidDelegationsByValidatorDenomRequest) (*types.SuperfluidDelegationsByValidatorDenomResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetSuperfluidAsset(ctx, req.Denom).Denom == "" {
return nil, types.ErrNonSuperfluidAsset
}

_, err := sdk.ValAddressFromBech32(req.ValidatorAddress)
if err != nil {
return nil, err
}

syntheticDenom := stakingSyntheticDenom(req.Denom, req.ValidatorAddress)

res := types.SuperfluidDelegationsByValidatorDenomResponse{
SuperfluidDelegationRecords: []types.SuperfluidDelegationRecord{},
}

periodLocks := k.lk.GetLocksLongerThanDurationDenom(ctx, syntheticDenom, time.Second)

for _, lock := range periodLocks {
lockedCoins := sdk.NewCoin(req.Denom, lock.GetCoins().AmountOf(req.Denom))
res.SuperfluidDelegationRecords = append(res.SuperfluidDelegationRecords,
types.SuperfluidDelegationRecord{
DelegatorAddress: lock.GetOwner(),
ValidatorAddress: req.ValidatorAddress,
DelegationAmount: lockedCoins,
},
)
}
return &res, nil
}

// EstimateSuperfluidDelegatedAmountByValidatorDenom returns the amount of a
// specific denom delegated to a specific validator
// This is labeled an estimate, because the way it calculates the amount can
// lead rounding errors from the true delegated amount
func (k Keeper) EstimateSuperfluidDelegatedAmountByValidatorDenom(goCtx context.Context, req *types.EstimateSuperfluidDelegatedAmountByValidatorDenomRequest) (*types.EstimateSuperfluidDelegatedAmountByValidatorDenomResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetSuperfluidAsset(ctx, req.Denom).Denom == "" {
return nil, types.ErrNonSuperfluidAsset
}

valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddress)
if err != nil {
return nil, err
}

intermediaryAcc, err := k.GetOrCreateIntermediaryAccount(ctx, req.Denom, req.ValidatorAddress)
if err != nil {
return nil, err
}

val, found := k.sk.GetValidator(ctx, valAddr)
if !found {
return nil, stakingtypes.ErrNoValidatorFound
}

delegation, found := k.sk.GetDelegation(ctx, intermediaryAcc.GetAccAddress(), valAddr)
if err != nil {
return nil, stakingtypes.ErrNoDelegation
}

syntheticOsmoAmt := delegation.Shares.Quo(val.DelegatorShares).MulInt(val.Tokens)

baseAmount := k.UnriskAdjustOsmoValue(ctx, syntheticOsmoAmt).Quo(k.GetOsmoEquivalentMultiplier(ctx, req.Denom)).RoundInt()
return &types.EstimateSuperfluidDelegatedAmountByValidatorDenomResponse{
TotalDelegatedCoins: sdk.NewCoins(sdk.NewCoin(req.Denom, baseAmount)),
}, nil
}
Loading

0 comments on commit 5be21bb

Please sign in to comment.