Skip to main content
Version: Next

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:

total supply of pASSETlastcASSET in vaultlast×cASSET to refract\frac{\text{total supply of pASSET}_{last}}{\text{cASSET in vault}_{last}} \times \text{cASSET to refract}

Here, total supply of pASSETlast\text{total supply of pASSET}_{last} represents the total supply of the principal tokens of this asset, irrespective of the maturity, before the ongoing refraction. cASSET in vaultlast\text{cASSET in vault}_{last} 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 total supply of pASSETlast\text{total supply of pASSET}_{last} and cASSET in vaultlast\text{cASSET in vault}_{last} parameters evolve with each refraction and redemption action. However, the ratio of total supply of pASSETlastcASSET in vaultlast\frac{\text{total supply of pASSET}_{last}}{\text{cASSET in vault}_{last}} remains constant. Yield distribution modifies the cAsset in vaultlast\text{cAsset in vault}_{last} without altering the total supply of pASSETlast\text{total supply of pASSET}_{last}, resulting in a change in the ratio. This process will be elaborated on in this page.

A unique case arises when cASSET in vaultlast\text{cASSET in vault}_{last} equates to zero, which occurs only when the total supply of pASSETlast\text{total supply of pASSET}_{last} 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 :

  1. 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.
  2. 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:

cASSET in vaultlasttotal supply of pASSETlast×pASSET burned\frac{\text{cASSET in vault}_{last}}{\text{total supply of pASSET}_{last}} \times \text{pASSET burned}

The pASSET burned\text{pASSET burned} 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 RefractorActions 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:

yield=cASSET in vaultlastcASSET in vaultlast×ElastEnow\text {yield} = \text{cASSET in vault}_{last} -\frac{\text{cASSET in vault}_{last} \times E_{last}}{E_{now}}

Here, ElastE_{last} refers to the previous ASSET:cASSET exchange rate, while EnowE_{now} is the current rate, as reported by the Oracle Module. cASSET in vaultlast\text{cASSET in vault}_{last} is the cASSET amount in the vault prior to the ongoing yield distribution. This equation is used when Elast<EnowE_{last} < E_{now}, ensuring a positive harvested yield. The yield is then divided into three parts: protocolFeeYield, stakeYield, and excessYield, computed as follows:

  1. Protocol fee:
 yield ×protocolFeeRatio \text{ yield } \times protocolFeeRatio

Here, protocolFeeRatioprotocolFeeRatio is a parameter within the [0,1)[0,1) 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.

  1. yStaking yield:
total staked unexpired yASSETstotal pASSETs×(yield×[1protocolFeeRatio]) {\text{total staked unexpired yASSETs} \over \text {total pASSETs}} \times (\text {yield} \times [1 - protocolFeeRatio])

Here, total staked unexpired yASSETs\text {total staked unexpired yASSETs} 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.

  1. 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:
excess yield to PRYZM holders=yieldprotocol feeYStaking yield\text {excess yield to PRYZM holders} = \text {yield} - \text {protocol fee} - \text{YStaking yield}

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
}