Skip to content

Commit

Permalink
return MaxUndelegatableAmount in the RPC QuerySingleDelegationInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
TimmyExogenous committed Feb 14, 2025
1 parent fd2c0a3 commit a1091c1
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 147 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
15 changes: 8 additions & 7 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 @@ -344,7 +345,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 +497,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
16 changes: 14 additions & 2 deletions x/delegation/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ import (

var _ delegationtype.QueryServer = &Keeper{}

func (k *Keeper) QuerySingleDelegationInfo(ctx context.Context, req *delegationtype.SingleDelegationInfoReq) (*delegationtype.DelegationAmounts, error) {
func (k *Keeper) QuerySingleDelegationInfo(ctx context.Context, req *delegationtype.SingleDelegationInfoReq) (*delegationtype.SingleDelegationInfoResponse, error) {
c := sdk.UnwrapSDKContext(ctx)
return k.GetSingleDelegationInfo(c, req.StakerId, req.AssetId, req.OperatorAddr)
delegationAmounts, err := k.GetSingleDelegationInfo(c, req.StakerId, req.AssetId, req.OperatorAddr)
if err != nil {
return nil, err
}
// calculate the maximum undelegatable amount
singleAmount, err := k.UndelegatableAmount(c, req.AssetId, req.OperatorAddr, delegationAmounts)
if err != nil {
return nil, err
}
return &delegationtype.SingleDelegationInfoResponse{
DelegationAmounts: delegationAmounts,
MaxUndelegatableAmount: singleAmount,
}, nil
}

func (k *Keeper) QueryDelegationInfo(ctx context.Context, info *delegationtype.DelegationInfoReq) (*delegationtype.QueryDelegationInfoResponse, error) {
Expand Down
Loading

0 comments on commit a1091c1

Please sign in to comment.