Refractor Module
Module Overview
The Refractor module serves two primary functions: the refraction of cASSETs into pASSETs and yASSETs, and the redemption of cASSETs. As cASSETs are refracted over time, a portion remains in the cASSET vault overseen by this module. Thus, this module also calculates and distributes the yields associated with the underlying assets in the vault.
Concepts
Refract
Refraction enables cASSET holders to break down their assets into their yield (yASSET) and principal (pASSET) components for a specific maturity. During refraction, the cASSET holder selects the standard (protocol-defined) maturity to mint. For example, PRYZM may offer Dec24, Jun25, Dec25, and Jun26. If the user selects Jun25, their cASSETs are placed in the cASSET VAULT, and they receive yASSETjun25 & pASSETjun25 tokens. The Assets Module provides the list of available maturities for each asset. The quantity of minted principle/yield tokens is calculated as follows:
Here, represents the total supply of the principal tokens of this asset, irrespective of the maturity, before the ongoing refraction. is the amount of this asset's cASSET in the Refractor module's cASSET Vault prior to the ongoing refraction. For example, if a user refracts 10 cLUNA for the maturity of Jun23, with a total pLUNA supply of 1000 and 500 cLUNA in the vault, the user receives 20 pLUNAJun23 and 20 yLUNAJun23. After this transaction, the total pLUNA supply becomes 1020, and the cLUNA in the vault increases to 510. Note that the Treasury Module may impose a fee for refraction, deducted initially from the input cASSET amount; the equations above apply to the remaining amount.
The and parameters evolve with each refraction and redemption action. However, the ratio of remains constant. Yield distribution modifies the without altering the , resulting in a change in the ratio. This process will be elaborated on in this page.
A unique case arises when equates to zero, which occurs only when the is also zero. In such cases, we utilize the ASSET:cASSET exchange rate provided by the Oracle Module.
Redeem
Redemption involves reclaiming cASSET from the cASSET vault by paying principal tokens or both principal and yield tokens. The type of tokens a user wants to pay—principal or yield—depends on the maturity :
- Upon maturity, the pASSET holder may redeem the equivalent amount of cASSET from the cASSET VAULT. For instance, post June 23, the pASSETJun23 holder can incinerate this token and reclaim a specific amount of cASSET.
- Before maturity, users must consolidate an equal amount of corresponding maturity yASSET and pASSET to reclaim cASSET from the vault. For example, a user possessing 10 yASSETJun23 and no other assets would need to purchase 10pASSETJun23. Armed with both tokens, they could then reclaim the corresponding cASSET quantity from the cASSET Vault.
The cASSET amount redeemed is calculated using the same ratio as Refract:
The is the quantity of pASSET the user expends to reclaim cASSETS. As previously explained, if the pASSET is pre-maturity, the user must also expend an equivalent amount of yASSET. According to the Treasury Module, there may be a redemption fee. This fee is first deducted from the redeemed cASSET amount, with the remaining cASSET transferred to the user.
Refractor Actions
The refractor module offers users a streamlined, efficient method for managing yield-bearing tokens. To utilize the refract/redeem capabilities of the refractor module via its keeper, two method sets are available.
The first set comprises Refract
and Redeem
, which execute the refract and redeem actions as previously described. These methods leverage the ComputeRedeem
and ComputeRefract
functions to calculate the parameters required for their respective actions. For example, ComputeRedeem
calculates the parameters for a redeem action, including the cAmount received, the fee deducted, and the remaining cAmount sent to the user. Similarly, ComputeRefract
determines the parameters for a refract action, such as the p/y assets minted, the fee deducted, and the remaining cAmount sent to the module account.
The second method set includes ComputeRedeem
and ComputeRefract
, which can be used to calculate a redeem/refract outcome and return a RefractorAction
. This process virtually mirrors the actual refract/redeem process, except that it doesn't execute the token transfer, mint, or burn actions - the chain's state remains unchanged.
After actions have been calculated, programmers can compile these actions into an AggregatedRefractorAction
and execute them using the keeper function ExecuteAggregatedAction
. The AggregatedRefractorAction
and RefractorAction
structures offer a method of managing and compiling multiple refract and redeem actions for yield-bearing tokens. The AggregatedRefractorAction
structure represents a group of RefractorAction
s amalgamated based on their asset, maturity level, and address. Each AggregatedRefractorAction
includes a map of maturityAggregatedRefractorAction
objects, which contain the aggregated action details for a specific maturity level.
The ExecuteAggregatedAction
function is a keeper function allowing users to execute a compiled set of refract/redeem actions. This function accepts an AggregatedRefractorAction
object as input and iterates through each maturity level to execute the required actions for each asset-maturity. In essence, the ExecuteAggregatedAction
function empowers users with a flexible, powerful method for executing multiple refract/redeem actions simultaneously, saving gas and decoupling the computation from the action's effect.
Yield Distribution
The Refractor module in the cASSET vault maintains an amount of cASSET, which is staked as an underlying ASSET. The module receives associated yields for the staked cASSET and manages their distribution. As the ASSET:cASSET exchange rate rises, the vault's cASSET value also increases in ASSET terms, enabling us to distribute some cASSET from the vault. The yield is calculated as follows:
Here, refers to the previous ASSET:cASSET exchange rate, while is the current rate, as reported by the Oracle Module. is the cASSET amount in the vault prior to the ongoing yield distribution. This equation is used when , ensuring a positive harvested yield. The yield is then divided into three parts: protocolFeeYield
, stakeYield
, and excessYield
, computed as follows:
- Protocol fee:
Here, is a parameter within the range, adjustable via governance proposals through the assets module. The treasury module collects this fee, stored in FeeRatios.Yield
of each asset in the assets module.
- yStaking yield:
Here, refers to yASSETs staked for the ASSET's active maturities in the yStaking module. The yStakingKeeper
provides this amount. The denominator is the total pASSETs, not the unexpired yASSETs, to account for the expired yASSETs. The community should receive the yield for the expired yASSETs.
- Excess yield goes to the Treasury Module for future distribution. This includes yield from "unstaked" yASSETs and yield accruing to expired yASSET holders past maturity. Thus, the excess yield is:
As the yield is distributed, the ratio used in Refract and Redeem equations changes to maintain the principal assets' value in terms of the underlying asset.
Parameters
The refractor module does not have any parameters.
Message
MsgRefract
This message refracts a cASSET amount into pASSET and yASSET based on the refraction equation.
message MsgRefract {
string creator = 1;
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.nullable) = false];
string maturity = 3;
}
The amount
parameter is a coin specifying the cASSET amount to refract. Its denom
should be the refractable asset's tokenDenom
. The maturity
parameter is the target maturity for obtaining p/y assets. The Assets Module can provide a list of an asset's available maturities.
If successful, the response will be in the following format, containing the actual minted principle and yield tokens.
message MsgRefractResponse {
cosmos.base.v1beta1.Coin p_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin y_amount = 2 [(gogoproto.nullable) = false];
}
You can execute this message via the following CLI command:
pryzmd tx refractor refract [amount] [maturity] --from [address]
MsgRedeem
The MsgRedeem
message allows for the redemption of a cASSET amount, paid with pASSET and yASSET as per the redeem equation.
message MsgRedeem {
string creator = 1;
cosmos.base.v1beta1.Coin p_amount = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin y_amount = 3;
}
p_amount
represents the principal tokens to redeem. If token maturity hasn't been reached, y_amount
, an equal quantity of yield tokens of the same asset and maturity as p_amount
, must be included. If the pAsset is mature, the yAsset isn't required for redemption, but any yAsset amount can be sent to be burnt.
A successful transaction yields the following response, detailing the redeemed cASSET amount in c_amount
.
message MsgRedeemResponse {
cosmos.base.v1beta1.Coin c_amount = 1 [(gogoproto.nullable) = false];
}
This message can also be executed via the following CLI command:
pryzmd tx refractor redeem [p-amount] [y-amount] --from [address]
Query
GetAssetStateRequest
This query fetches the current state of an asset, including its total pASSET and last known exchange rate.
message QueryGetAssetStateRequest {
string asset_id = 1;
}
message QueryGetAssetStateResponse {
AssetState asset_state = 1 [(gogoproto.nullable) = false];
}
GetCPExchangeRateRequest
This query fetches the exchange rate between cASSET and p/yASSET for a specific asset.
message QueryGetCPExchangeRateRequest {
string asset_id = 1;
}
message QueryGetCPExchangeRateResponse {
string exchange_rate = 1 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
SimulateRefract
This query computes outputs of a refract action.
message QuerySimulateRefractRequest {
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false];
string maturity = 2;
}
message QuerySimulateRefractResponse {
cosmos.base.v1beta1.Coin p_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin y_amount = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 3 [(gogoproto.nullable) = false];
}
SimulateRedeem
This query computes outputs of a redeem action.
message QuerySimulateRedeemRequest {
cosmos.base.v1beta1.Coin p_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin y_amount = 2;
}
message QuerySimulateRedeemResponse {
cosmos.base.v1beta1.Coin c_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}
Events
EventRefract
EventRefract
is triggered when a refraction is successful:
message EventRefract {
string creator = 1;
string c_amount = 2;
string maturity = 3;
string refracted_p_amount = 4;
string refracted_y_amount = 5;
string fee = 6;
}
EventRedeem
EventRedeem
is triggered when a redemption is successful:
message EventRedeem {
string creator = 1;
string p_amount = 2;
string y_amount = 3;
string maturity = 4;
string redeemed_c_amount = 5;
string fee = 6;
}
EventRefractorYieldDistribution
Refer to yield distribution for details. When the ASSET:cASSET exchange rate for an asset increases, yield is harvested and divided into three pools: protocolFeeYield
, stakeYield
, and excessYield
. EventRefractorYieldDistribution
is triggered after each successful yield distribution, sharing information on the total yield and each pool's yield amount.
message EventRefractorYieldDistribution {
string asset_base_denom = 1;
string total_yield = 2;
string protocol_fee = 3;
string stake_yield = 4;
string excess_yield = 5;
}
EventSetAssetState
This event broadcasts when an AssetState object is stored.
message EventSetAssetState {
AssetState asset_state = 1 [(gogoproto.nullable) = false];
}
Listeners
OracleListener
The Refractor module necessitates an OracleListener
from the Oracle Module to distribute yield as the asset's exchange rate fluctuates. This module also implements the ExchangeRateUpdated
method:
func (l *RefractorOracleListener) ExchangeRateUpdated(ctx sdk.Context, assetBaseDenom string,
oldExchangeRate *sdk.Dec, newExchangeRate sdk.Dec) error {
return l.k.updateExchangeRateAndDistributeReward(ctx, assetBaseDenom, newExchangeRate)
}
YieldListener
Upon distributing yields for each pool by the Refractor module, a listener method gets triggered to notify other modules of the yield accrual event. These methods are not called unless the related sum is positive.
// YieldListener is used for publishing yield events
type YieldListener interface {
ProtocolFeeYieldAccrued(ctx sdk.Context, assetBaseDenom string, amount sdk.Coin) error
StakeYieldAccrued(ctx sdk.Context, assetBaseDenom string, amount sdk.Coin) error
ExcessYieldAccrued(ctx sdk.Context, assetBaseDenom string, amount sdk.Coin) error
}
State
The AssetState
struct maintains the total number of pAssets for all maturities of an asset, which also includes the latest exchange rate of the asset. This information is crucial for calculating the actual pAsset/cAsset exchange rate, tracking exchange rate changes, and determining the yield to distribute.
message AssetState {
string total_p_amount = 1 [(cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"];
string last_seen_exchange_rate = 2 [(cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}
The AssetState
for an asset is stored using a key of the form:
[]byte("v1/asset-state/") | []byte(assetBaseDenom)
Genesis
The genesis state for the Refractor includes a list of AssetState
objects.
// GenesisState defines the refractor module's genesis state.
message GenesisState {
repeated AssetState asset_state_list = 1 [(gogoproto.nullable) = false];
// this line is used by starport scaffolding # genesis/proto/state
}