Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed fixed per-tranche mint cap #206

Merged
merged 11 commits into from
May 30, 2024
152 changes: 108 additions & 44 deletions spot-contracts/contracts/PerpetualTranche.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/t
import { IERC20Upgradeable, IPerpetualTranche, IBondIssuer, IFeePolicy, IBondController, ITranche } from "./_interfaces/IPerpetualTranche.sol";
import { IRolloverVault } from "./_interfaces/IRolloverVault.sol";
import { TokenAmount, RolloverData, SubscriptionParams } from "./_interfaces/CommonTypes.sol";
import { UnauthorizedCall, UnauthorizedTransferOut, UnexpectedDecimals, UnexpectedAsset, UnacceptableParams, UnacceptableRollover, ExceededMaxSupply, ExceededMaxMintPerTranche, ReserveCountOverLimit } from "./_interfaces/ProtocolErrors.sol";
import { UnauthorizedCall, UnauthorizedTransferOut, UnexpectedDecimals, UnexpectedAsset, UnacceptableParams, UnacceptableRollover, ExceededMaxSupply, ExceededMaxMintPerTranche, ReserveCountOverLimit, InvalidPerc } from "./_interfaces/ProtocolErrors.sol";

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
Expand Down Expand Up @@ -107,9 +107,10 @@ contract PerpetualTranche is
//
//-------------------------------------------------------------------------
// Constants & Immutables
// Number of decimals for a multiplier of 1.0x (i.e. 100%)
uint8 public constant FEE_POLICY_DECIMALS = 8;
uint256 public constant FEE_ONE = (10 ** FEE_POLICY_DECIMALS);

/// @dev Internal percentages are fixed point numbers with {PERC_DECIMALS} places.
uint8 public constant PERC_DECIMALS = 8;
uint256 public constant ONE = (10 ** PERC_DECIMALS); // 1.0 or 100%

/// @dev The maximum number of reserve assets that can be held by perp.
uint8 public constant MAX_RESERVE_COUNT = 11;
Expand Down Expand Up @@ -170,11 +171,15 @@ contract PerpetualTranche is
/// @notice The maximum supply of perps that can exist at any given time.
uint256 public maxSupply;

/// @notice The max number of perps that can be minted using the senior tranche in the minting bond.
uint256 public maxMintAmtPerTranche;
/// @notice Enforced maximum percentage of reserve value in the deposit tranche.
/// @custom:oz-upgrades-renamed-from maxMintAmtPerTranche
uint256 public maxDepositTrancheValuePerc;

/// @notice The total number of perps that have been minted using a given tranche.
mapping(ITranche => uint256) public mintedSupplyPerTranche;
/// @notice DEPRECATED.
/// @dev This used to store the number of perps minted using each deposit tranche.
/// @custom:oz-upgrades-renamed-from mintedSupplyPerTranche
// solhint-disable-next-line var-name-mixedcase
mapping(ITranche => uint256) private _mintedSupplyPerTranche_DEPRECATED;

/// @notice DEPRECATED.
/// @dev This used to store the discount factor applied on each reserve token.
Expand Down Expand Up @@ -266,7 +271,8 @@ contract PerpetualTranche is
updateBondIssuer(bondIssuer_);

updateTolerableTrancheMaturity(1, type(uint256).max);
updateMintingLimits(type(uint256).max, type(uint256).max);
updateMaxSupply(type(uint256).max);
updateMaxDepositTrancheValuePerc(ONE);
}

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -296,7 +302,7 @@ contract PerpetualTranche is
/// @notice Update the reference to the fee policy contract.
/// @param feePolicy_ New strategy address.
function updateFeePolicy(IFeePolicy feePolicy_) public onlyOwner {
if (feePolicy_.decimals() != FEE_POLICY_DECIMALS) {
if (feePolicy_.decimals() != PERC_DECIMALS) {
revert UnexpectedDecimals();
}
feePolicy = feePolicy_;
Expand Down Expand Up @@ -346,12 +352,20 @@ contract PerpetualTranche is
_unpause();
}

/// @notice Update parameters controlling the perp token mint limits.
/// @notice Updates the maximum supply.
/// @param maxSupply_ New max total supply.
/// @param maxMintAmtPerTranche_ New max total for per tranche in minting bond.
function updateMintingLimits(uint256 maxSupply_, uint256 maxMintAmtPerTranche_) public onlyKeeper {
function updateMaxSupply(uint256 maxSupply_) public onlyKeeper {
maxSupply = maxSupply_;
maxMintAmtPerTranche = maxMintAmtPerTranche_;
}

/// @notice Updates the enforced maximum percentage value of deposit tranches.
/// @dev Stored as a fixed point number with {PERC_DECIMALS} places.
/// @param maxDepositTrancheValuePerc_ New max percentage.
function updateMaxDepositTrancheValuePerc(uint256 maxDepositTrancheValuePerc_) public onlyKeeper {
if (maxDepositTrancheValuePerc_ > ONE) {
revert InvalidPerc();
}
maxDepositTrancheValuePerc = maxDepositTrancheValuePerc_;
}

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -380,7 +394,6 @@ contract PerpetualTranche is
_mint(msg.sender, perpAmtMint);

// post-deposit checks
mintedSupplyPerTranche[trancheIn] += perpAmtMint;
_enforceMintCaps(trancheIn);

return perpAmtMint;
Expand Down Expand Up @@ -491,7 +504,14 @@ contract PerpetualTranche is

ITranche tranche = ITranche(address(token));
IBondController parentBond = IBondController(tranche.bond());
return _computeReserveTrancheValue(tranche, parentBond, _reserveAt(0), tranche.balanceOf(address(this)), true);
return
_computeReserveTrancheValue(
tranche,
parentBond,
_reserveAt(0),
tranche.balanceOf(address(this)),
MathUpgradeable.Rounding.Up
);
}

/// @inheritdoc IPerpetualTranche
Expand Down Expand Up @@ -566,6 +586,18 @@ contract PerpetualTranche is
return _computeRolloverAmt(trancheIn, tokenOut, trancheInAmtAvailable);
}

/// @inheritdoc IPerpetualTranche
function deviationRatio() external override afterStateUpdate nonReentrant returns (uint256) {
return
feePolicy.computeDeviationRatio(
SubscriptionParams({
perpTVL: _reserveValue(),
vaultTVL: vault.getTVL(),
seniorTR: _depositBond.getSeniorTrancheRatio()
})
);
}

//--------------------------------------------------------------------------
// Public methods

Expand Down Expand Up @@ -681,16 +713,25 @@ contract PerpetualTranche is
// Removes tranche from reserve set.
_reserves.remove(address(token));

// Frees up minted supply.
delete mintedSupplyPerTranche[ITranche(address(token))];
// Frees up storage slot used by existing tranches in the system.
// NOTE: This variable in the process of being `DEPRECATED`, and the following line
// can be removed once all storage used by all the current tranches are zeroed out
// as they are removed from the reserve.
delete _mintedSupplyPerTranche_DEPRECATED[ITranche(address(token))];
}

return balance;
}

/// @dev Computes the fee adjusted perp mint amount for given amount of tranche tokens deposited into the reserve.
function _computeMintAmt(ITranche trancheIn, uint256 trancheInAmt) private view returns (uint256) {
uint256 valueIn = _computeReserveTrancheValue(trancheIn, _depositBond, _reserveAt(0), trancheInAmt, false);
uint256 valueIn = _computeReserveTrancheValue(
trancheIn,
_depositBond,
_reserveAt(0),
trancheInAmt,
MathUpgradeable.Rounding.Down
);

//-----------------------------------------------------------------------------
// We charge no mint fee when interacting with other callers within the system.
Expand All @@ -706,7 +747,7 @@ contract PerpetualTranche is

// The mint fees are settled by simply minting fewer perps.
if (feePerc > 0) {
perpAmtMint = perpAmtMint.mulDiv(FEE_ONE - feePerc, FEE_ONE);
perpAmtMint = perpAmtMint.mulDiv(ONE - feePerc, ONE);
}

return perpAmtMint;
Expand All @@ -733,7 +774,7 @@ contract PerpetualTranche is

// The burn fees are settled by simply redeeming for fewer tranches.
if (feePerc > 0) {
reserveTokens[i].amount = reserveTokens[i].amount.mulDiv(FEE_ONE - feePerc, FEE_ONE);
reserveTokens[i].amount = reserveTokens[i].amount.mulDiv(ONE - feePerc, ONE);
}
}

Expand Down Expand Up @@ -767,7 +808,13 @@ contract PerpetualTranche is
// with the same number of decimals.
IERC20Upgradeable underlying_ = _reserveAt(0);
uint256 unitTokenAmt = (10 ** _decimals);
uint256 trancheInPrice = _computeReserveTrancheValue(trancheIn, _depositBond, underlying_, unitTokenAmt, false);
uint256 trancheInPrice = _computeReserveTrancheValue(
trancheIn,
_depositBond,
underlying_,
unitTokenAmt,
MathUpgradeable.Rounding.Down
);
uint256 tokenOutPrice = unitTokenAmt;
if (tokenOut != underlying_) {
ITranche trancheOut = ITranche(address(tokenOut));
Expand All @@ -776,7 +823,7 @@ contract PerpetualTranche is
IBondController(trancheOut.bond()),
underlying_,
unitTokenAmt,
true
MathUpgradeable.Rounding.Up
);
}

Expand All @@ -800,12 +847,12 @@ contract PerpetualTranche is
// A positive fee percentage implies that perp charges rotators by
// offering tranchesOut for a premium, i.e) more tranches in.
if (feePerc > 0) {
r.trancheInAmt = r.trancheInAmt.mulDiv(FEE_ONE, FEE_ONE - feePerc.toUint256(), MathUpgradeable.Rounding.Up);
r.trancheInAmt = r.trancheInAmt.mulDiv(ONE, ONE - feePerc.toUint256(), MathUpgradeable.Rounding.Up);
}
// A negative fee percentage (or a reward) implies that perp pays the rotators by
// offering tranchesOut at a discount, i.e) fewer tranches in.
else if (feePerc < 0) {
r.trancheInAmt = r.trancheInAmt.mulDiv(FEE_ONE, FEE_ONE + feePerc.abs(), MathUpgradeable.Rounding.Up);
r.trancheInAmt = r.trancheInAmt.mulDiv(ONE, ONE + feePerc.abs(), MathUpgradeable.Rounding.Up);
}

//-----------------------------------------------------------------------------
Expand All @@ -822,13 +869,13 @@ contract PerpetualTranche is
// accepting tranchesIn at a discount, i.e) fewer tokens out.
// This results in perp enrichment.
if (feePerc > 0) {
r.tokenOutAmt = r.tokenOutAmt.mulDiv(FEE_ONE - feePerc.abs(), FEE_ONE);
r.tokenOutAmt = r.tokenOutAmt.mulDiv(ONE - feePerc.abs(), ONE);
}
// A negative fee percentage (or a reward) implies that perp pays the rotators by
// accepting tranchesIn at a premium, i.e) more tokens out.
// This results in perp debasement.
else if (feePerc < 0) {
r.tokenOutAmt = r.tokenOutAmt.mulDiv(FEE_ONE + feePerc.abs(), FEE_ONE);
r.tokenOutAmt = r.tokenOutAmt.mulDiv(ONE + feePerc.abs(), ONE);
}
}

Expand Down Expand Up @@ -887,17 +934,41 @@ contract PerpetualTranche is
}

/// @dev Enforces the total supply and per tranche mint cap. To be invoked AFTER the mint operation.
function _enforceMintCaps(ITranche trancheIn) private view {
// checks if supply minted using the given tranche is within the cap
if (mintedSupplyPerTranche[trancheIn] > maxMintAmtPerTranche) {
revert ExceededMaxMintPerTranche();
}

// checks if new total supply is within the max supply cap
function _enforceMintCaps(ITranche depositTranche) private view {
// Checks if new total supply is within the max supply cap
uint256 newSupply = totalSupply();
if (newSupply > maxSupply) {
revert ExceededMaxSupply();
}

// Checks if the value of deposit tranche relative to the other tranches in the reserve
// is no higher than the defined limit.
//
// NOTE: We consider the tranches which are up for rollover and mature collateral (if any),
// to be part of the deposit tranche, as given enough time
// they will be eventually rolled over into the deposit tranche.
IERC20Upgradeable underlying_ = _reserveAt(0);
uint256 totalVal = underlying_.balanceOf(address(this));
uint256 depositTrancheValue = totalVal;
uint8 reserveCount = uint8(_reserves.length());
for (uint8 i = 1; i < reserveCount; ++i) {
ITranche tranche = ITranche(address(_reserveAt(i)));
uint256 trancheValue = _computeReserveTrancheValue(
brandoniles marked this conversation as resolved.
Show resolved Hide resolved
tranche,
IBondController(tranche.bond()),
underlying_,
tranche.balanceOf(address(this)),
MathUpgradeable.Rounding.Up
);
if (tranche == depositTranche || _isTimeForRollout(tranche)) {
depositTrancheValue += trancheValue;
}
totalVal += trancheValue;
}
uint256 depositTrancheValuePerc = depositTrancheValue.mulDiv(ONE, totalVal, MathUpgradeable.Rounding.Up);
if (depositTrancheValuePerc > maxDepositTrancheValuePerc) {
revert ExceededMaxMintPerTranche();
}
}

/// @dev Fetches the reserve token by index.
Expand All @@ -924,7 +995,7 @@ contract PerpetualTranche is
parentBond,
underlying_,
tranche.balanceOf(address(this)),
true
MathUpgradeable.Rounding.Up
);
}
return totalVal;
Expand All @@ -938,22 +1009,15 @@ contract PerpetualTranche is
IBondController parentBond,
IERC20Upgradeable collateralToken,
uint256 trancheAmt,
bool roundUp
MathUpgradeable.Rounding rounding
) private view returns (uint256) {
// NOTE: As an optimization here, we assume that the reserve tranche is immature and has the most senior claim.
uint256 parentBondCollateralBalance = collateralToken.balanceOf(address(parentBond));
uint256 trancheSupply = tranche.totalSupply();
uint256 trancheClaim = MathUpgradeable.min(trancheSupply, parentBondCollateralBalance);
// Tranche supply is zero (its parent bond has no deposits yet);
// the tranche's CDR is assumed 1.0.
return
(trancheSupply > 0)
? trancheClaim.mulDiv(
trancheAmt,
trancheSupply,
roundUp ? MathUpgradeable.Rounding.Up : MathUpgradeable.Rounding.Down
)
: trancheAmt;
return (trancheSupply > 0) ? trancheClaim.mulDiv(trancheAmt, trancheSupply, rounding) : trancheAmt;
}

/// @dev Checks if the given token is the underlying collateral token.
Expand Down
Loading