Skip to content

Commit

Permalink
feat(delegation): query maximum undelegation amount (#285)
Browse files Browse the repository at this point in the history
* return MaxUndelegatableAmount in the RPC QuerySingleDelegationInfo

* rebase and convert the gRPC input to lowercase for assets,delegation,opeartor modules
  • Loading branch information
TimmyExogenous authored Feb 17, 2025
1 parent 290519d commit 73f004c
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 160 deletions.
6 changes: 2 additions & 4 deletions client/docs/statik/statik.go

Large diffs are not rendered by default.

50 changes: 43 additions & 7 deletions client/docs/swagger-ui/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -20816,24 +20816,35 @@
},
"/exocore/delegation/v1/single_delegation/{staker_id}/{operator_addr}/{asset_id}": {
"get": {
"summary": "SingleDelegationInfo queries the single delegation information for\n{chain, staker, asset, operator}.",
"summary": "SingleDelegationInfo queries the single delegation information and the\nmaximum undelegatable amount for {staker, asset, operator}.",
"operationId": "QuerySingleDelegationInfo",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"type": "object",
"properties": {
"undelegatable_share": {
"type": "string",
"description": "undelegatable_share is the share that can be undelegated.\nIt's to reduce the state updating when slash occurs.\nS_j = S * T_j / T, `S` and `T` is the current asset share and amount of operator,\nand the T_j represents the change in staker's asset amount when some external\noperations occur, such as: delegation, undelegation and slashing.\nS_j represents the change in the staker's asset share,\nso the updated share should be added by it.\nA special case is the initial delegation, when T = 0 and S = 0, so T_j / T is undefined.\nFor the initial delegation, delegator j who delegates T_j tokens receive S_j = T_j shares."
"delegation_amounts": {
"type": "object",
"properties": {
"undelegatable_share": {
"type": "string",
"description": "undelegatable_share is the share that can be undelegated.\nIt's to reduce the state updating when slash occurs.\nS_j = S * T_j / T, `S` and `T` is the current asset share and amount of operator,\nand the T_j represents the change in staker's asset amount when some external\noperations occur, such as: delegation, undelegation and slashing.\nS_j represents the change in the staker's asset share,\nso the updated share should be added by it.\nA special case is the initial delegation, when T = 0 and S = 0, so T_j / T is undefined.\nFor the initial delegation, delegator j who delegates T_j tokens receive S_j = T_j shares."
},
"wait_undelegation_amount": {
"type": "string",
"description": "wait_undelegation_amount is the amount that is waiting to be unbonded."
}
},
"description": "DelegationAmounts is the delegation amount response for a single delegation.",
"title": "delegation_amounts is the delegation info recorded in the KVStore"
},
"wait_undelegation_amount": {
"max_undelegatable_amount": {
"type": "string",
"description": "wait_undelegation_amount is the amount that is waiting to be unbonded."
"title": "max_undelegatable_amount is the maximum amount that can be undelegated"
}
},
"description": "DelegationAmounts is the delegation amount response for a single delegation."
"title": "SingleDelegationInfoResponse is the response to QuerySingleDelegationInfo"
}
},
"default": {
Expand Down Expand Up @@ -43411,6 +43422,31 @@
},
"description": "QueryDelegationInfoResponse is the response for delegations by staker id and\nasset id."
},
"exocore.delegation.v1.SingleDelegationInfoResponse": {
"type": "object",
"properties": {
"delegation_amounts": {
"type": "object",
"properties": {
"undelegatable_share": {
"type": "string",
"description": "undelegatable_share is the share that can be undelegated.\nIt's to reduce the state updating when slash occurs.\nS_j = S * T_j / T, `S` and `T` is the current asset share and amount of operator,\nand the T_j represents the change in staker's asset amount when some external\noperations occur, such as: delegation, undelegation and slashing.\nS_j represents the change in the staker's asset share,\nso the updated share should be added by it.\nA special case is the initial delegation, when T = 0 and S = 0, so T_j / T is undefined.\nFor the initial delegation, delegator j who delegates T_j tokens receive S_j = T_j shares."
},
"wait_undelegation_amount": {
"type": "string",
"description": "wait_undelegation_amount is the amount that is waiting to be unbonded."
}
},
"description": "DelegationAmounts is the delegation amount response for a single delegation.",
"title": "delegation_amounts is the delegation info recorded in the KVStore"
},
"max_undelegatable_amount": {
"type": "string",
"title": "max_undelegatable_amount is the maximum amount that can be undelegated"
}
},
"title": "SingleDelegationInfoResponse is the response to QuerySingleDelegationInfo"
},
"exocore.delegation.v1.UndelegationAndHoldCount": {
"type": "object",
"properties": {
Expand Down
27 changes: 20 additions & 7 deletions proto/exocore/delegation/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ message QueryDelegationInfoResponse {
map<string, DelegationAmounts> delegation_infos = 1;
}

// SingleDelegationInfoReq is the request to obtain the single delegation information.
// SingleDelegationInfoReq is the request to obtain the single delegation information
// and the maximum undelegatable amount of specific delegation.
message SingleDelegationInfoReq {
// staker_id is the staker id.
string staker_id = 1;
Expand All @@ -68,14 +69,26 @@ message SingleDelegationInfoReq {
string asset_id = 3;
}

// SingleDelegationInfoResponse is the response to QuerySingleDelegationInfo
message SingleDelegationInfoResponse {
// delegation_amounts is the delegation info recorded in the KVStore
DelegationAmounts delegation_amounts = 1;
// max_undelegatable_amount is the maximum amount that can be undelegated
string max_undelegatable_amount = 2 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}

// UndelegationHoldCountReq is the request to obtain the undelegation hold count.
message UndelegationHoldCountReq {
// staker_id is the staker id.
string staker_id = 1;
// asset_id is the asset id.
string asset_id = 2;
// undelegation_id is the undelegation id
uint64 undelegation_id =3;
uint64 undelegation_id = 3;
}

// UndelegationHoldCountResponse is the response for the undelegation hold count.
Expand Down Expand Up @@ -106,9 +119,9 @@ message UndelegationsByEpochInfoReq {
// hold count, which is used to construct the genesis state
message UndelegationAndHoldCount {
// undelegation is the single undelegation record
UndelegationRecord undelegation =1;
UndelegationRecord undelegation = 1;
// hold_count represents the number of holds on this undelegation
uint64 hold_count =2;
uint64 hold_count = 2;
}

// UndelegationRecordList is the response to query undelegations.
Expand Down Expand Up @@ -164,9 +177,9 @@ service Query {
option (cosmos.query.v1.module_query_safe) = true;
option (google.api.http).get = "/exocore/delegation/v1/delegations/{staker_id}/{asset_id}";
}
// SingleDelegationInfo queries the single delegation information for
// {chain, staker, asset, operator}.
rpc QuerySingleDelegationInfo(SingleDelegationInfoReq) returns (DelegationAmounts) {
// SingleDelegationInfo queries the single delegation information and the
// maximum undelegatable amount for {staker, asset, operator}.
rpc QuerySingleDelegationInfo(SingleDelegationInfoReq) returns (SingleDelegationInfoResponse) {
option (cosmos.query.v1.module_query_safe) = true;
option (google.api.http).get = "/exocore/delegation/v1/single_delegation/{staker_id}/{operator_addr}/{asset_id}";
}
Expand Down
12 changes: 1 addition & 11 deletions testutil/batch/tx_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package batch
import (
"math/big"

delegationkeeper "github.com/ExocoreNetwork/exocore/x/delegation/keeper"
sdktypes "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
Expand Down Expand Up @@ -56,16 +55,7 @@ func (m *Manager) QueryDelegatedAmount(clientChainLzID uint64, stakerAddr, asset
if err != nil {
return sdkmath.ZeroInt(), err
}
operatorAssetReq := &assettypes.QueryOperatorSpecifiedAssetAmountReq{
OperatorAddr: operatorAddr, // already lowercase
AssetId: assetID, // already lowercase
}
queryAssetsClient := assettypes.NewQueryClient(m.NodeClientCtx[DefaultNodeIndex])
operatorAssetInfo, err := queryAssetsClient.QueOperatorSpecifiedAssetAmount(m.ctx, operatorAssetReq)
if err != nil {
return sdkmath.ZeroInt(), err
}
return delegationkeeper.TokensFromShares(delegationInfo.UndelegatableShare, operatorAssetInfo.TotalShare, operatorAssetInfo.TotalAmount)
return delegationInfo.MaxUndelegatableAmount, nil
}

func (m *Manager) PrecompileTxOnChainCheck(batchID uint, msgType string) error {
Expand Down
9 changes: 5 additions & 4 deletions x/assets/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"strings"

"github.com/cosmos/cosmos-sdk/store/prefix"
"github.com/cosmos/cosmos-sdk/types/query"
Expand Down Expand Up @@ -57,7 +58,7 @@ func (k Keeper) QueAllClientChainInfo(goCtx context.Context, req *assetstype.Que
// QueStakingAssetInfo query the specified client chain asset info by inputting assetID
func (k Keeper) QueStakingAssetInfo(ctx context.Context, info *assetstype.QueryStakingAssetInfo) (*assetstype.StakingAssetInfo, error) {
c := sdk.UnwrapSDKContext(ctx)
return k.GetStakingAssetInfo(c, info.AssetId)
return k.GetStakingAssetInfo(c, strings.ToLower(info.AssetId))
}

// QueAllStakingAssetsInfo query the info about all client chain assets that have been registered
Expand All @@ -73,7 +74,7 @@ func (k Keeper) QueAllStakingAssetsInfo(ctx context.Context, _ *assetstype.Query
// QueStakerAssetInfos query th state of all assets for a staker specified by stakerID
func (k Keeper) QueStakerAssetInfos(ctx context.Context, info *assetstype.QueryStakerAssetInfo) (*assetstype.QueryAssetInfoResponse, error) {
c := sdk.UnwrapSDKContext(ctx)
assetInfos, err := k.GetStakerAssetInfos(c, info.StakerId)
assetInfos, err := k.GetStakerAssetInfos(c, strings.ToLower(info.StakerId))
if err != nil {
return nil, err
}
Expand All @@ -83,7 +84,7 @@ func (k Keeper) QueStakerAssetInfos(ctx context.Context, info *assetstype.QueryS
// QueStakerSpecifiedAssetAmount query the specified asset state of a staker, using stakerID and assetID as query parameters
func (k Keeper) QueStakerSpecifiedAssetAmount(ctx context.Context, req *assetstype.QuerySpecifiedAssetAmountReq) (*assetstype.StakerAssetInfo, error) {
c := sdk.UnwrapSDKContext(ctx)
return k.GetStakerSpecifiedAssetInfo(c, req.StakerId, req.AssetId)
return k.GetStakerSpecifiedAssetInfo(c, strings.ToLower(req.StakerId), strings.ToLower(req.AssetId))
}

// QueOperatorAssetInfos query th state of all assets for an operator specified by operator address
Expand All @@ -107,5 +108,5 @@ func (k Keeper) QueOperatorSpecifiedAssetAmount(ctx context.Context, req *assets
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return k.GetOperatorSpecifiedAssetInfo(c, addr, req.AssetId)
return k.GetOperatorSpecifiedAssetInfo(c, addr, strings.ToLower(req.AssetId))
}
16 changes: 8 additions & 8 deletions x/delegation/keeper/delegation_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
assetskeeper "github.com/ExocoreNetwork/exocore/x/assets/keeper"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"

"github.com/ExocoreNetwork/exocore/x/assets/types"
delegationtype "github.com/ExocoreNetwork/exocore/x/delegation/types"
Expand Down Expand Up @@ -102,9 +103,9 @@ func (suite *DelegationTestSuite) prepareOptingInDogfood(assetID string) (sdkmat

func (suite *DelegationTestSuite) prepareDelegationNativeToken() *delegationtype.DelegationOrUndelegationParams {
delegationEvent := &delegationtype.DelegationOrUndelegationParams{
ClientChainID: types.ExocoreChainLzID,
ClientChainID: assetstypes.ExocoreChainLzID,
Action: types.DelegateTo,
AssetsAddress: common.HexToAddress(types.ExocoreAssetAddr).Bytes(),
AssetsAddress: common.HexToAddress(assetstypes.ExocoreAssetAddr).Bytes(),
OperatorAddress: suite.opAccAddr,
StakerAddress: suite.accAddr[:],
OpAmount: suite.delegationAmount,
Expand Down Expand Up @@ -176,9 +177,9 @@ func (suite *DelegationTestSuite) TestDelegateTo() {

// delegate exocore-native-token
delegationParams = &delegationtype.DelegationOrUndelegationParams{
ClientChainID: types.ExocoreChainLzID,
ClientChainID: assetstypes.ExocoreChainLzID,
Action: types.DelegateTo,
AssetsAddress: common.HexToAddress(types.ExocoreAssetAddr).Bytes(),
AssetsAddress: common.HexToAddress(assetstypes.ExocoreAssetAddr).Bytes(),
OperatorAddress: opAccAddr,
StakerAddress: suite.accAddr[:],
OpAmount: sdkmath.NewInt(50),
Expand All @@ -190,7 +191,7 @@ func (suite *DelegationTestSuite) TestDelegateTo() {
stakerID, assetID = types.GetStakerIDAndAssetID(delegationParams.ClientChainID, delegationParams.StakerAddress, delegationParams.AssetsAddress)
restakerState, err = suite.App.AssetsKeeper.GetStakerSpecifiedAssetInfo(suite.Ctx, stakerID, assetID)
suite.NoError(err)
balance := suite.App.BankKeeper.GetBalance(suite.Ctx, suite.accAddr, types.ExocoreAssetDenom)
balance := suite.App.BankKeeper.GetBalance(suite.Ctx, suite.accAddr, assetstypes.ExocoreAssetDenom)
suite.Equal(types.StakerAssetInfo{
TotalDepositAmount: balance.Amount.Add(delegationParams.OpAmount),
WithdrawableAmount: balance.Amount,
Expand Down Expand Up @@ -272,7 +273,6 @@ func (suite *DelegationTestSuite) TestAutoAssociate() {
TotalShare: sdkmath.LegacyNewDecFromBigInt(delegationParams.OpAmount.BigInt()),
OperatorShare: sdkmath.LegacyNewDecFromBigInt(delegationParams.OpAmount.BigInt()),
}, *operatorState)

}

func (suite *DelegationTestSuite) TestUndelegateFrom() {
Expand Down Expand Up @@ -344,7 +344,7 @@ func (suite *DelegationTestSuite) TestUndelegateFrom() {
stakerID, assetID = types.GetStakerIDAndAssetID(delegationEvent.ClientChainID, delegationEvent.StakerAddress, delegationEvent.AssetsAddress)
restakerState, err = suite.App.AssetsKeeper.GetStakerSpecifiedAssetInfo(suite.Ctx, stakerID, assetID)
suite.NoError(err)
balance := suite.App.BankKeeper.GetBalance(suite.Ctx, suite.accAddr, types.ExocoreAssetDenom)
balance := suite.App.BankKeeper.GetBalance(suite.Ctx, suite.accAddr, assetstypes.ExocoreAssetDenom)
suite.Equal(types.StakerAssetInfo{
TotalDepositAmount: balance.Amount.Add(delegationEvent.OpAmount),
WithdrawableAmount: balance.Amount,
Expand Down Expand Up @@ -496,7 +496,7 @@ func (suite *DelegationTestSuite) TestCompleteUndelegation() {
restakerState, err = suite.App.AssetsKeeper.GetStakerSpecifiedAssetInfo(suite.Ctx, stakerID, assetID)
suite.NoError(err)

balance := suite.App.BankKeeper.GetBalance(suite.Ctx, suite.accAddr, types.ExocoreAssetDenom)
balance := suite.App.BankKeeper.GetBalance(suite.Ctx, suite.accAddr, assetstypes.ExocoreAssetDenom)
suite.Equal(types.StakerAssetInfo{
TotalDepositAmount: balance.Amount,
WithdrawableAmount: balance.Amount,
Expand Down
30 changes: 16 additions & 14 deletions x/delegation/keeper/delegation_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ func (k Keeper) IterateDelegationsForStaker(ctx sdk.Context, stakerID string, op
return k.IterateDelegations(ctx, []byte(stakerID), opFunc)
}

func (k Keeper) UndelegatableAmount(ctx sdk.Context, assetID, operator string, amounts *delegationtype.DelegationAmounts) (amount sdkmath.Int, err error) {
opAccAddr := sdk.MustAccAddressFromBech32(operator)
// get the asset state of operator
operatorAsset, err := k.assetsKeeper.GetOperatorSpecifiedAssetInfo(ctx, opAccAddr, assetID)
if err != nil {
return sdkmath.ZeroInt(), err
}
singleAmount, err := TokensFromShares(amounts.UndelegatableShare, operatorAsset.TotalShare, operatorAsset.TotalAmount)
if err != nil {
return sdkmath.ZeroInt(), err
}
return singleAmount, nil
}

// TotalDelegatedAmountForStakerAsset query the total delegation amount of the specified staker and asset.
// It needs to be calculated from the share and amount of the asset pool.
func (k Keeper) TotalDelegatedAmountForStakerAsset(ctx sdk.Context, stakerID string, assetID string) (amount sdkmath.Int, err error) {
Expand All @@ -80,13 +94,7 @@ func (k Keeper) TotalDelegatedAmountForStakerAsset(ctx sdk.Context, stakerID str
if amounts.UndelegatableShare.IsZero() {
return false, nil
}
opAccAddr := sdk.MustAccAddressFromBech32(keys.GetOperatorAddr())
// get the asset state of operator
operatorAsset, err := k.assetsKeeper.GetOperatorSpecifiedAssetInfo(ctx, opAccAddr, assetID)
if err != nil {
return true, err
}
singleAmount, err := TokensFromShares(amounts.UndelegatableShare, operatorAsset.TotalShare, operatorAsset.TotalAmount)
singleAmount, err := k.UndelegatableAmount(ctx, assetID, keys.GetOperatorAddr(), amounts)
if err != nil {
return true, err
}
Expand All @@ -102,13 +110,7 @@ func (k Keeper) TotalDelegatedAmountForStakerAsset(ctx sdk.Context, stakerID str
func (k *Keeper) AllDelegatedInfoForStakerAsset(ctx sdk.Context, stakerID string, assetID string) (map[string]sdkmath.Int, error) {
ret := make(map[string]sdkmath.Int)
opFunc := func(keys *delegationtype.SingleDelegationInfoReq, amounts *delegationtype.DelegationAmounts) (bool, error) {
opAccAddr := sdk.MustAccAddressFromBech32(keys.GetOperatorAddr())
// get the asset state of operator
operatorAsset, err := k.assetsKeeper.GetOperatorSpecifiedAssetInfo(ctx, opAccAddr, assetID)
if err != nil {
return true, err
}
singleAmount, err := TokensFromShares(amounts.UndelegatableShare, operatorAsset.TotalShare, operatorAsset.TotalAmount)
singleAmount, err := k.UndelegatableAmount(ctx, assetID, keys.GetOperatorAddr(), amounts)
if err != nil {
return true, err
}
Expand Down
Loading

0 comments on commit 73f004c

Please sign in to comment.