Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion spot-contracts/contracts/BondIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O
import { EnumerableSetUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
import { BondHelpers } from "./_utils/BondHelpers.sol";

/// @notice Expected tranche ratios to sum up to {TRANCHE_RATIO_GRANULARITY}.
error UnacceptableTrancheRatios();

/**
* @title BondIssuer
*
Expand Down Expand Up @@ -102,7 +105,10 @@ contract BondIssuer is IBondIssuer, OwnableUpgradeable {
for (uint8 i = 0; i < trancheRatios_.length; i++) {
ratioSum += trancheRatios_[i];
}
require(ratioSum == TRANCHE_RATIO_GRANULARITY, "BondIssuer: Invalid tranche ratios");

if (ratioSum > TRANCHE_RATIO_GRANULARITY) {
revert UnacceptableTrancheRatios();
Comment thread
brandoniles marked this conversation as resolved.
}
}

/// @notice Updates the bond frequency and offset.
Expand Down
76 changes: 53 additions & 23 deletions spot-contracts/contracts/FeePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
pragma solidity ^0.8.19;

import { IFeePolicy } from "./_interfaces/IFeePolicy.sol";
import { IPerpetualTranche, IBondController } from "./_interfaces/IPerpetualTranche.sol";
import { IVault } from "./_interfaces/IVault.sol";

import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Sigmoid } from "./_utils/Sigmoid.sol";

import { UnacceptableSwap } from "./_interfaces/ProtocolErrors.sol";
/// @notice Expected perc value to be at most (1 * 10**DECIMALS), i.e) 1.0 or 100%.
error InvalidPerc();

/// @notice Expected target subscription ratio to be within defined bounds.
error InvalidTargetSRBounds();

/// @notice Expected sigmoid asymptotes to be within defined bounds.
error InvalidSigmoidAsymptotes();

/**
* @title FeePolicy
Expand All @@ -24,7 +28,7 @@ import { UnacceptableSwap } from "./_interfaces/ProtocolErrors.sol";
*
* Fees are computed based on the deviation between the system's current subscription ratio
* and the target subscription ratio.
* - `subscriptionRatio` = (vaultTVL * perpTR) / (perpTVL * vaultTR)
* - `subscriptionRatio` = (vaultTVL * seniorTR) / (perpTVL * 1-seniorTR)
* - `deviationRatio` (dr) = subscriptionRatio / targetSubscriptionRatio
*
* When the system is "under-subscribed" (dr <= 1):
Expand Down Expand Up @@ -58,13 +62,19 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
using MathUpgradeable for uint256;
using SafeCastUpgradeable for uint256;

// Replicating value used here:
// https://github.com/buttonwood-protocol/tranche/blob/main/contracts/BondController.sol
uint256 private constant TRANCHE_RATIO_GRANULARITY = 1000;

/// @dev The returned fee percentages are fixed point numbers with {DECIMALS} places.
/// The decimals should line up with value expected by consumer (perp, vault).
/// NOTE: 10**DECIMALS => 100% or 1.0
uint8 public constant DECIMALS = 8;
uint256 public constant ONE = (1 * 10**DECIMALS); // 1.0 or 100%

/// @dev SIGMOID_BOUND is set to 1%, i.e) the rollover fee can be at most 1% on either direction.
uint256 public constant SIGMOID_BOUND = ONE / 100; // 0.01 or 1%

uint256 public constant SR_LOWER_BOUND = (ONE * 75) / 100; // 0.75 or 75%
uint256 public constant SR_UPPER_BOUND = 2 * ONE; // 2.0 or 200%

Expand Down Expand Up @@ -149,33 +159,40 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// @notice Updates the target subscription ratio.
/// @param targetSubscriptionRatio_ The new target subscription ratio as a fixed point number with {DECIMALS} places.
function updateTargetSubscriptionRatio(uint256 targetSubscriptionRatio_) external onlyOwner {
require(targetSubscriptionRatio_ > SR_LOWER_BOUND, "FeeStrategy: sr too low");
require(targetSubscriptionRatio_ <= SR_UPPER_BOUND, "FeeStrategy: sr high low");
if (targetSubscriptionRatio_ < SR_LOWER_BOUND || targetSubscriptionRatio_ > SR_UPPER_BOUND) {
revert InvalidTargetSRBounds();
}
targetSubscriptionRatio = targetSubscriptionRatio_;
}

/// @notice Updates the perp mint fee parameters.
/// @param perpMintFeePerc_ The new perp mint fee ceiling percentage
/// as a fixed point number with {DECIMALS} places.
function updatePerpMintFees(uint256 perpMintFeePerc_) external onlyOwner {
require(perpMintFeePerc_ <= ONE, "FeeStrategy: perc too high");
if (perpMintFeePerc_ > ONE) {
revert InvalidPerc();
}
perpMintFeePerc = perpMintFeePerc_;
}

/// @notice Updates the perp burn fee parameters.
/// @param perpBurnFeePerc_ The new perp burn fee ceiling percentage
/// as a fixed point number with {DECIMALS} places.
function updatePerpBurnFees(uint256 perpBurnFeePerc_) external onlyOwner {
require(perpBurnFeePerc_ <= ONE, "FeeStrategy: perc too high");
if (perpBurnFeePerc_ > ONE) {
revert InvalidPerc();
}
perpBurnFeePerc = perpBurnFeePerc_;
}

/// @notice Update the parameters determining the slope and asymptotes of the sigmoid fee curve.
/// @param p Lower, Upper and Growth sigmoid paramters are fixed point numbers with {DECIMALS} places.
function updatePerpRolloverFees(RolloverFeeSigmoidParams calldata p) external onlyOwner {
require(p.lower >= -int256(SIGMOID_BOUND), "FeeStrategy: sigmoid lower bound too low");
require(p.upper <= int256(SIGMOID_BOUND), "FeeStrategy: sigmoid upper bound too high");
require(p.lower <= p.upper, "FeeStrategy: sigmoid asymptotes invalid");
// If the bond duration is 28 days and 13 rollovers happen per year,
Comment thread
brandoniles marked this conversation as resolved.
// perp can be inflated or enriched up to ~13% annually.
if (p.lower < -int256(SIGMOID_BOUND) || p.upper > int256(SIGMOID_BOUND) || p.lower > p.upper) {
revert InvalidSigmoidAsymptotes();
}
perpRolloverFee.lower = p.lower;
perpRolloverFee.upper = p.upper;
perpRolloverFee.growth = p.growth;
Expand All @@ -185,15 +202,19 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// @param vaultMintFeePerc_ The new vault mint fee ceiling percentage
/// as a fixed point number with {DECIMALS} places.
function updateVaultMintFees(uint256 vaultMintFeePerc_) external onlyOwner {
require(vaultMintFeePerc_ <= ONE, "FeeStrategy: perc too high");
if (vaultMintFeePerc_ > ONE) {
revert InvalidPerc();
}
vaultMintFeePerc = vaultMintFeePerc_;
}

/// @notice Updates the vault burn fee parameters.
/// @param vaultBurnFeePerc_ The new vault burn fee ceiling percentage
/// as a fixed point number with {DECIMALS} places.
function updateVaultBurnFees(uint256 vaultBurnFeePerc_) external onlyOwner {
require(vaultBurnFeePerc_ <= ONE, "FeeStrategy: perc too high");
if (vaultBurnFeePerc_ > ONE) {
revert InvalidPerc();
}
vaultBurnFeePerc = vaultBurnFeePerc_;
}

Expand All @@ -206,14 +227,18 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// @notice Updates the vault's share of the underlying to perp swap fee.
/// @param feePerc The new fee percentage.
function updateVaultUnderlyingToPerpSwapFeePerc(uint256 feePerc) external onlyOwner {
require(feePerc <= ONE, "FeeStrategy: perc too high");
if (feePerc > ONE) {
revert InvalidPerc();
}
vaultUnderlyingToPerpSwapFeePerc = feePerc;
}

/// @notice Updates the vault's share of the perp to underlying swap fee.
/// @param feePerc The new fee percentage.
function updateVaultPerpToUnderlyingSwapFeePerc(uint256 feePerc) external onlyOwner {
require(feePerc <= ONE, "FeeStrategy: perc too high");
if (feePerc > ONE) {
revert InvalidPerc();
}
vaultPerpToUnderlyingSwapFeePerc = feePerc;
}

Expand Down Expand Up @@ -286,13 +311,18 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
}

/// @inheritdoc IFeePolicy
function computeDeviationRatio(IFeePolicy.SubscriptionParams memory s) external view returns (uint256) {
function computeDeviationRatio(IFeePolicy.SubscriptionParams memory s) public view returns (uint256) {
return computeDeviationRatio(s.perpTVL, s.vaultTVL, s.seniorTR);
}

/// @inheritdoc IFeePolicy
function computeDeviationRatio(
uint256 perpTVL,
uint256 vaultTVL,
uint256 seniorTR
) public view returns (uint256) {
// NOTE: We assume that perp's TVL and vault's TVL values have the same base denomination.
return
s.vaultTVL.mulDiv(s.perpTR, (s.perpTVL * s.vaultTR), MathUpgradeable.Rounding.Up).mulDiv(
ONE,
targetSubscriptionRatio,
MathUpgradeable.Rounding.Up
);
uint256 juniorTR = TRANCHE_RATIO_GRANULARITY - seniorTR;
return (vaultTVL * seniorTR).mulDiv(ONE, (perpTVL * juniorTR)).mulDiv(ONE, targetSubscriptionRatio);
}
}
Loading