Minting and Redeeming Junior Tranche tokens (ERC-20)
Please refer to the SmartYield.sol repo for code references made on this page.
SMART Yield relies on minting and burning ERC-20 tokens to keep track of users' claims on the junior tranche of a given originator.
Upon depositing, the user receives a commensurate number of fungible tokens that reflects the exchange value between one such token and the underlying stablecoin.
event BuyTokens(address indexed buyer, uint256 underlyingIn, uint256 tokensOut, uint256 fee);
SMART Yield junior tranche tokens all have a common format indicating that they are a BarnBridge product built on top of an existing platform (e.g., bb_cUSDC for USDC deposits into Compound Finance).
Upon redemption, the user burns the ERC-20 tokens in exchange for a proportional subset of the junior tranche, minus a possible redemption fee (i.e., the "forfeits" variable below) depending on what the weighted average maturity date of the senior tranche is.
event SellTokens(address indexed seller, uint256 tokensIn, uint256 underlyingOut, uint256 forfeits);
Deposits into the junior tranches of all SMART Yield junior tranches assess a nominal fee on the principal.
uint256 fee = MathUtils.fractionOf(underlyingAmount_, IController(controller).FEE_BUY_JUNIOR_TOKEN());
The junior tranche deposit fee is currently set to 0.5%, or 50 basis points, of the principal. This fee is then directed to the address set as "feesOwner" in IController.sol. In practice, this is the DAO's treasury address, which also allows for governance votes to adjust the fee size.
With the fee assessed, the appropriate number of jTokens for their deposit sum can be determined.
uint256 getsTokens = (underlyingAmount_.sub(fee)).mul(EXP_SCALE).div(price());
The primary price of a given jToken is determined by the
price()
function.function price()
public override
returns (uint256)
{
uint256 ts = totalSupply();
// (ts == 0) ? EXP_SCALE : (underlyingJuniors() * EXP_SCALE) / ts
return (ts == 0) ? EXP_SCALE : underlyingJuniors().mul(EXP_SCALE).div(ts);
}
Assuming that checks are passed (i.e., the contract is not paused and the getsTokens variable is within range), the user is then able to exchange their stablecoins for jTokens. (See: IProvider.sol)
address buyer = msg.sender;
IProvider(pool)._takeUnderlying(buyer, underlyingAmount_);
IProvider(pool)._depositProvider(underlyingAmount_, fee);
_mint(buyer, getsTokens);
emit BuyTokens(buyer, underlyingAmount_, getsTokens, fee);
The junior tranche of any given SMART Yield pool is earning yield in part to cover the fixed yields of the senior tranche. In order to ensure that there remains enough capital in the system to cover what is owed to senior tranche tokens, the
forfeits
variable keeps track of the obligation for a given subset of the junior tranche.forfeits
is calculated within the sellTokens
function upon a user's attempt to redeem their jTokens.First, the
debtShare
variable is calculated to determine what percentage of the outstanding junior tranche token supply a potential sale amount represents. uint256 debtShare = tokenAmount_.mul(EXP_SCALE).div(totalSupply());
With the user's debt share determined, SMART Yield is then able to assess the proportionally correct value for
forfeits
which then gets subtracted from the value of the user's jTokens within the toPay
variable.uint256 forfeits = abondDebt().mul(debtShare).div(EXP_SCALE);
uint256 toPay = tokenAmount_.mul(price()).div(EXP_SCALE).sub(forfeits);
The
abond
variable contains the necessary rate of payment by the junior tranche to the senior tranche at that given point in time; abondDebt()
calls the difference between what yield is owed to outstanding senior tranche bonds and how much yield has been generated thus far.Users are able to redeem their jTokens in one of two ways: either immediately, and be obligated to pay their respective
forfeits
sum, or via a slower two-step redemption process.Single-step redemption allows the user to claim their principal and earned yield immediately, but potentially at the expense of forfeiting a portion necessary for covering senior tranche obligations. (See: IProvider.sol)
address seller = msg.sender;
_burn(seller, tokenAmount_);
IProvider(pool)._withdrawProvider(toPay, 0);
IProvider(pool)._sendUnderlying(seller, toPay);
emit SellTokens(seller, tokenAmount_, toPay, forfeits);
There is no redemption fee charged on the junior tranche.
Two-step redemption allows the user to avoid forfeiting capital, but requires them to wait until the weighted average maturity date for the senior tranche at the time of initiating the process. This process involves the exchange of jTokens for a junior tranche ERC-721 bond, akin to the senior tranche token process.
When the user elects to redeem via this method, their maturity date is determined as the day following the aforementioned senior tranche, or ABOND, date.
uint256 maturesAt = abond.maturesAt.div(EXP_SCALE).add(1);
Upon maturity of the jToken bond, the user is then able to redeem the original sum of jTokens they had by burning the associated ERC-721.
address payTo = IBond(juniorBond).ownerOf(jBondId_);
uint256 payAmnt = jBondsAt.price.mul(jb.tokens).div(EXP_SCALE);
_burnJuniorBond(jBondId_);
IProvider(pool)._withdrawProvider(payAmnt, 0);
IProvider(pool)._sendUnderlying(payTo, payAmnt);
underlyingLiquidatedJuniors = underlyingLiquidatedJuniors.sub(payAmnt);
emit RedeemJuniorBond(payTo, jBondId_, payAmnt);