yStaking Module
Module Overview
The YStaking
module of the PRYZM network empowers users to yield rewards through staking their yASSET. The key functions of this module include "Bond", "Unbond", and "ClaimReward". These functions facilitate user participation in the staking process, allow withdrawal of their stake, and claim earned rewards respectively.
Concepts
YAsset Pool
A yAsset pool manages staked yield tokens of a specific asset in the YStaking module. Each yAsset pool tracks the bondedAmount
and globalIndex
for an ASSET. Note that, bondedAmount
represents the total yASSET amount of a specific ASSET across all active maturity levels (expired maturities are not included in the bondedAmount
). The globalIndex
value, used for reward calculations, indicates the reward amount accrued to the pool relative to the bondedAmount
. When a reward of amount is accrued to the pool, the globalIndex
is updated as follows:
Here, and are the globalIndex
values before and after the reward respectively, and is the bondedAmount
for the pool. The initial value of the globalIndex
is .
To manage different asset maturities, the yAsset pool also keeps track of certain parameters for each maturity using MaturityYAssetPool
pools. A yAsset pool maintains a registry of active and inactive maturities related to its MaturityYAssetPool
s. The pool marks maturities as inactive when the Assets Module triggers the listeners for maturity deactivation. More details about deactivating a maturity are explained in the listeners section. The yAsset pool also provides an API for retrieving a MaturityYAssetPool
by its symbol and active state.
Maturity YAsset Pool
Each yAsset pool maintains a set of maturity yAsset pools according to the asset maturities. A maturity yAsset pool holds the bondedAmount
and globalIndex
values for its specific asset and maturity. Additionally, it tracks staked tokens for users. For each user, a maturity yAsset pool holds bondedAmount
, userIndex
, and pendingReward
(if they have some bonded amount or reward to claim). The bondedAmount
represents the precise staked amount of yASSET with the specific maturity of this pool for a specific user. The userIndex
and pendingReward
are used for reward computations and will be explained in subsequent sections. A maturity yAsset pool provides APIs for bond
, unbond
, claimReward
, and exitPool
.
Bond
Bonding refers to a user staking a specific amount of a yAsset of a specific maturity. The bond API is provided by MaturityYAssetPool
, which can be accessed from the related YAssetPool
by providing the maturity symbol. Notably, bonding is only available for active maturities, and tokens of expired maturities cannot be bonded. When a user bonds a value of to a MaturityYAssetPool
, the parameters of the related pools are updated as follows:
- The
bondedAmount
of theYAssetPool
increases by the bonded amount:
Here, and represent the bondedAmount
parameter before and after the ongoing bond respectively.
- The
bondedAmount
of theMaturityYAssetPool
also increases by the bonding amount. - The
pendingReward
for the user in the specificMaturityYAssetPool
is updated as:
In this formula, and denote the pendingReward
for user before and after the bond related to this specific asset and maturity. The term signifies the bondedAmount
for user of this particular asset and maturity before the bond. The variable represents the globalIndex
of the asset, while signifies the userIndex
for user prior to this bond. Note: The initial value for is , and the initial value for is equal to .
- The
bondedAmount
of the user within theMaturityYAssetPool
also increases by the bond amount.
- The
userIndex
for this asset and maturity matches theglobalIndex
of the asset.
Unbond
Unbonding involves a user unstaking a quantity of a yAsset of a specific maturity. As previously mentioned, the Unbond API is supplied by MaturityYAssetPool
, which can be retrieved from the corresponding YAssetPool
by providing the maturity symbol. Note: Unbonding is only permitted for active maturities. For expired maturities, use the ExitPool function. A user's unbonding of value from a MaturityYAssetPool
updates the parameters of the related pools as follows, and may result in a reward being paid to the user:
- The
bondedAmount
of theYAssetPool
is reduced by the unbonded amount:
Here, and depict the bondedAmount
parameter before and after the unbonding respectively.
- The
bondedAmount
of theMaturityYAssetPool
also decreases by the unbonded amount. - The user receives a reward for their bonded amount up until this unbond:
In this equation, designates the reward amount to be paid. Note: As outlined in the Treasury module, a fee is deducted from this reward, with the remainder paid to the user.
- The
pendingReward
for the user in the specificMaturityYAssetPool
is reset to zero:
- The
bondedAmount
of the user in theMaturityYAssetPool
is also reduced by the unbonded amount.
- The
userIndex
for this asset and maturity matches theglobalIndex
of the asset.
It's important to note that if a user's new bonded amount reaches zero after unbonding, the user state will be removed to minimize storage footprint.
ClaimReward
Claiming a reward is a process where a user can claim the accrued reward for their bonded amount of a specific asset and maturity. The MaturityYAssetPool
provides the ClaimReward API, which is accessible from the corresponding YAssetPool
by passing the maturity symbol. Keep in mind, claiming rewards is only possible for active maturities. For expired maturities, users must utilize the ExitPool method. When a user claims a reward for a particular asset and maturity, several actions occur:
- The user receives the appropriate reward for their bonded amount until this unbond:
Here, denotes the reward amount to be paid. Note: As discussed in the Treasury Module, a fee is deducted from this reward, and the remainder is paid to the user.
- The user's
pendingReward
in the specificMaturityYAssetPool
is reset to zero:
- The
userIndex
for this asset and maturity synchronizes with theglobalIndex
of the asset.
ExitPool
To claim rewards for expired maturities, users need to use the ExitPool method. This process pays the user the amount of the related reward and removes the data maintained for their bonded amount of this specific asset at the specific maturity from the state. The ExitPool API is provided by MaturityYAssetPool
, accessible from the corresponding YAssetPool
by passing the maturity symbol. This process is only available for inactive maturities. When a user exits an expired MaturityYAssetPool
, the related pools' parameters are updated as follows, and a reward may be paid to the user:
- The
bondedAmount
of theMaturityYAssetPool
is also decreased by the total amount of bonded amount of the user:
- The user receives the appropriate reward for their bonded amount until this unbond:
Here, represents the reward amount to be paid. Note: As explained in the Treasury Module, a fee is deducted from this reward, and the remainder is paid to the user.
- All the user's parameters stored in the
MaturityYAssetPool
are deleted.
Exiting an expired maturity pool won't affect the bondedAmount
of the related yAsset pool. As previously mentioned, the bondedAmount
in yAsset pools represents the bonded amount only for the active maturities. The listeners section will explain that an expired maturity pool's entire bonded amount is removed from the yAssetPool as soon as the pool expires.
Parameters
The refractor module doesn't have any parameters.
Message
MsgBond
This message allows staking a yASSET amount.
message MsgBond {
string creator = 1;
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.nullable) = false];
}
The amount parameter is the yASSET to stake and must be of an active maturity.
If this transaction is successful, the response will be as follows, containing the total bonded amount of this specific yASSET for the user after the bond process.
message MsgBondResponse {
cosmos.base.v1beta1.Coin total_bonded_amount = 1 [(gogoproto.nullable) = false];
}
Use the following command to implement this message through the command line interface (CLI):
pryzmd tx ystaking bond [amount] --from [address]
MsgUnbond
Use this message to unbond a certain amount of yASSET from a mature yAsset pool.
message MsgUnbond {
string creator = 1;
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.nullable) = false];
}
The amount parameter indicates the yASSET to unbond, which should belong to an active maturity.
If the transaction is successful, the response will contain the remaining bonded amount of this specific yASSET for the user after the process, the reward paid to the user, and the fee deducted from the reward.
message MsgUnbondResponse {
cosmos.base.v1beta1.Coin remainder_bonded_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin accrued_reward = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 3 [(gogoproto.nullable) = false];
}
Implement this message through the CLI with the following command:
pryzmd tx ystaking unbond [amount] --from [address]
MsgClaimReward
Use this message to claim rewards from a mature yAsset pool.
message MsgClaimReward {
string creator = 1;
string denom = 2;
}
The denom parameter denotes the target yASSET to claim rewards for and should be from an active maturity.
If the transaction is successful, the response will contain the reward amount paid to the user and the fee deducted from the reward.
message MsgClaimRewardResponse {
cosmos.base.v1beta1.Coin accrued_reward = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}
Implement this message through the CLI with this command:
pryzmd tx ystaking claim-reward [denom] --from [address]
MsgExitPool
Use this message to claim rewards and exit from an expired yAsset pool.
message MsgExitPool {
string creator = 1;
string denom = 2;
}
The denom parameter denotes the target yASSET for which you are claiming rewards and should be from an expired maturity.
If the transaction is successful, the response will contain the reward amount paid to the user and the fee deducted from the reward.
message MsgExitPoolResponse {
cosmos.base.v1beta1.Coin accrued_reward = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}
Use this command to implement this message through the CLI:
pryzmd tx ystaking exit-pool [denom] --from [address]
Query
QueryBondedAmount
The bonded-amount query returns the bonded amount for a specific asset. If the maturity parameter is provided, the response will be the bonded amount for the asset of the specified maturity. The address parameter is optional; if provided, the result will be the bonded amount for the user of the specified address. Note: the asset parameter is mandatory, and the maturity parameter is required if the address is provided.
message QueryBondedAmountRequest {
string asset = 1;
string maturity = 2;
string address = 3;
}
message QueryBondedAmountResponse {
string amount = 1 [(cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
}
To employ this query via command-line interface (CLI), use:
pryzmd query ystaking bonded-amount [asset] [maturity] [address]
Note: The maturity
and address
parameters are optional. This command accepts 1 to 3 parameters.
QueryReward
This query returns the accrued reward for staked tokens of a specified denom for a specific address.
message QueryRewardRequest {
string denom = 1;
string address = 2;
}
message QueryRewardResponse {
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false];
}
The CLI command for this query is:
pryzmd query ystaking reward [denom] [address]
QueryGetUserStakeStateRequest
This query retrieves the stake state of a user for a specific asset and maturity symbol.
message QueryGetUserStakeStateRequest {
string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string asset_id = 2;
string maturity_symbol = 3;
}
message QueryGetUserStakeStateResponse {
UserStakeState user_stake_state = 1 [(gogoproto.nullable) = false];
}
The CLI command for this query is:
pryzmd query ystaking show-user-stake-state [address] [assetId] [maturitySymbol]
QueryAllUserStakeStateRequest
This query retrieves all user stake states with optional pagination. Note that address
and asset_id
are optional, however if asset_id
is provided, address
is required.
message QueryAllUserStakeStateRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1 [(gogoproto.nullable) = true];
string address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string asset_id = 3;
}
message QueryAllUserStakeStateResponse {
repeated UserStakeState user_stake_state = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}
The CLI command for this query is:
pryzmd query ystaking list-user-stake-state
QueryGetAssetPoolStateRequest
This query retrieves the pool state of a specific asset.
message QueryGetAssetPoolStateRequest {
string asset_id = 1;
}
message QueryGetAssetPoolStateResponse {
AssetPoolState asset_pool_state = 1 [(gogoproto.nullable) = false];
}
The CLI command for this query is:
pryzmd query ystaking show-asset-pool-state [assetId]
QueryAllAssetPoolStateRequest
This query retrieves all asset pool states with optional pagination.
message QueryAllAssetPoolStateRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1 [(gogoproto.nullable) = true];
}
message QueryAllAssetPoolStateResponse {
repeated AssetPoolState asset_pool_state = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}
The CLI command for this query is:
pryzmd query ystaking list-asset-pool-state
QueryGetAssetMaturityPoolStateRequest
This query retrieves the maturity pool state of a specific asset and maturity symbol.
message QueryGetAssetMaturityPoolStateRequest {
string asset_id = 1;
string maturity_symbol = 2;
}
message QueryGetAssetMaturityPoolStateResponse {
AssetMaturityPoolState asset_maturity_pool_state = 1 [(gogoproto.nullable) = false];
}
The CLI command for this query is:
pryzmd query ystaking show-asset-maturity-pool-state [assetId] [maturitySymbol]
QueryAllAssetMaturityPoolStateRequest
This query retrieves all asset maturity pool states with optional pagination.
message QueryAllAssetMaturityPoolStateRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1 [(gogoproto.nullable) = true];
}
message QueryAllAssetMaturityPoolStateResponse {
repeated AssetMaturityPoolState asset_maturity_pool_state = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}
The CLI command for this query is:
pryzmd query ystaking list-asset-maturity-pool-state
Events
EventYStakingBond
Triggered when a bond operation is successful:
message EventYStakingBond {
string account_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.nullable) = false];
}
EventYStakingUnbond
Triggered when an unbonding operation is successful:
message EventYStakingUnbond {
string account_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin accrued_reward = 3 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 4 [(gogoproto.nullable) = false];
}
EventYStakingClaimReward
Triggered when a reward claim operation is successful:
message EventYStakingClaimReward {
string account_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
cosmos.base.v1beta1.Coin accrued_reward = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 3 [(gogoproto.nullable) = false];
}
EventYStakingExitPool
Triggered when an exit pool operation is successful:
message EventYStakingExitPool {
string account_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
cosmos.base.v1beta1.Coin accrued_reward = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 3 [(gogoproto.nullable) = false];
}
EventDeactivateYStakingMaturityPool
Triggered when a maturity pool expires. Upon expiration, the pool's total bonded amount is burnt and removed from the yAssetPool's total bonded amount.
message EventDeactivateYStakingMaturityPool {
cosmos.base.v1beta1.Coin burnt_bonded_amount = 1 [(gogoproto.nullable) = false];
string asset_id = 2;
string maturity_symbol = 3;
}
EventSetUserStakeState
Triggered when a user's stake state is set:
message EventSetUserStakeState {
UserStakeState user_stake_state = 1 [(gogoproto.nullable) = false];
}
EventSetAssetPoolState
Triggered when an asset pool state is set:
message EventSetAssetPoolState {
AssetPoolState asset_pool_state = 1 [(gogoproto.nullable) = false];
}
EventSetAssetMaturityPoolState
Triggered when an asset maturity pool state is set:
message EventSetAssetMaturityPoolState {
AssetMaturityPoolState asset_maturity_pool_state = 1 [(gogoproto.nullable) = false];
}
EventDeleteUserStakeState
Triggered when a user's stake state is deleted:
message EventDeleteUserStakeState {
string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string asset_id = 2;
string maturity_symbol = 3;
}
EventDeleteAssetPoolState
Triggered when an asset pool state is deleted:
message EventDeleteAssetPoolState {
string asset_id = 1;
}
EventDeleteAssetMaturityPoolState
Triggered when an asset maturity pool state is deleted:
message EventDeleteAssetMaturityPoolState {
string asset_id = 1;
string maturity_symbol = 2;
}
Listeners
MaturityLevelListener
The YStaking module implements the MaturityLevelListener
from the Assets Module to deactivate maturity yAsset pools as they expire. This listener implements the MaturityLevelDeactivated
method:
func (l *MaturityLevelListener) MaturityLevelDeactivated(ctx sdk.Context, maturityLevel assets.MaturityLevel) error {
pool, err := l.k.assetPool(ctx, maturityLevel.Asset)
if err != nil {
return err
}
return pool.DeactivateMaturityAssetPoolIfExists(ctx, maturityLevel.Symbol)
}
Upon receiving the event, the relevant yAsset pool takes the necessary actions, assuming any state exists for the maturity asset pool.
- Withdraw the bonded yAsset amount from the
BondedPool
. - Update the global index of the corresponding asset pool on the maturity asset pool. Note that the global index of a
MaturityYAssetPool
remainsnil
while the maturity is active. Thus, an activeMaturityYAssetPool
employs the global index of the correspondingYAssetPool
to fetch the latest index value. However, once a maturity expires, its global index is frozen at the global index of the correspondingYAssetPool
at that moment and will never update, hence it won't receive any reward thereafter. - Reduce the bonded amount of the
YAssetPool
by the bonded amount of theMaturityYAssetPool
. Consequently, the bonded amount ofYAssetPool
will only signify the number of active maturities. - Mark the
MaturityYAssetPool
as inactive in its registry.
YieldListener
The YStaking module needs to monitor yield and calculate rewards. This module implements the YieldListener
of the Refractor Module and employs the StakeYieldAccrued
method when any yield is accrued:
func (l *YStakingYieldListener) StakeYieldAccrued(ctx sdk.Context, assetBaseDenom string, amount sdk.Coin) error {
pool, err := l.k.assetPool(ctx, assetBaseDenom)
if err != nil {
return err
}
return pool.UpdateGlobalIndex(amount)
}
As previously explained, the global index facilitates reward tracking and is updated using the equation:
Here, and represent the global index of the yAsset pool, before and after receiving a reward of amount when the active bonded amount is . This update is applicable only to the global index for the associated yAsset pool and not the maturity asset pools. As stated earlier, the active maturity asset pools do not maintain their global index but derive it from the related yAsset pool. This approach eliminates the need to iterate through all active maturity pools for updating their global index each time yield is accrued.
State
YAssetPool
A YAssetPool's state is stored as an AssetPoolState
object with the following properties, further detailed in the concepts section.
message AssetPoolState {
string asset_id = 1;
string bonded_amount = 2 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string global_index = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
The AssetPoolState
for an asset pool is stored with a key in the format:
(
[]byte("v1/parent-yasset-pool/") |
[]byte(len(assetId)) |
[]byte(assetId) |
) -> ProtoBuf(AssetPoolState)
AssetMaturityPool
An AssetMaturityPoolState
object, further detailed in the concepts section, stores the state of a maturity yAsset pool.
message AssetMaturityPoolState {
string asset_id = 1;
string maturity_symbol = 2;
bool active = 3;
string bonded_amount = 4 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string global_index = 5 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = true
];
}
The AssetMaturityPoolState
is stored with a key of the form:
(
[]byte("v1/maturity-yasset-pool/") |
[]byte(len(assetId)) |
[]byte(bytes(assetId)) |
[]byte(len(maturitySymbol)) |
[]byte(bytes(maturitySymbol)) |
) -> ProtoBuf(AssetPoolState)
UserStakeState
Every yAsset bond for each user is stored as a UserStakeState
:
message UserStakeState {
string address = 1;
string asset_id = 2;
string maturity_symbol = 3;
string bonded_amount = 4 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string user_index = 5 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string pending_reward = 6 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}
The key for a specific user's state is:
(
[]byte("v1/user-stake/") |
byte(len(address)) |
[]byte(address) |
[]byte(len(assetId)) |
[]byte(bytes(assetId)) |
[]byte(len(maturitySymbol)) |
[]byte(bytes(maturitySymbol))
) -> ProtoBuf(UserStakeState)
Genesis
The YStaking module's genesis state holds all pool data and user-stakes:
// GenesisState defines the ystaking module's genesis state.
message GenesisState {
repeated AssetPoolState asset_pool_state_list = 1 [(gogoproto.nullable) = false];
repeated AssetMaturityPoolState maturity_pool_state_list = 2 [(gogoproto.nullable) = false];
repeated UserStakeState user_stake_state_list = 3 [(gogoproto.nullable) = false];
// this line is used by starport scaffolding # genesis/proto/state
}