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
60 changes: 34 additions & 26 deletions spot-contracts/contracts/FeePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SubscriptionParams } from "./_interfaces/CommonTypes.sol";
import { InvalidPerc, InvalidTargetSRBounds, InvalidDRBounds } from "./_interfaces/ProtocolErrors.sol";

import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import { SignedMathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SignedMathUpgradeable.sol";
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
Expand Down Expand Up @@ -96,21 +97,22 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {

/// @dev NOTE: We updated the type of the parameters from int256 to uint256, which is an upgrade safe operation.
struct RolloverFeeParams {
/// @notice The maximum debasement rate for perp,
/// i.e) the maximum rate perp pays the vault for rollovers.
/// @notice The minimum rollover fee percentage enforced by the contract.
/// @dev This is represented as signed fixed point number with {DECIMALS} places.
/// The rollover fee percentage returned by the fee policy will be no lower than
/// this specified value.
/// The parameter effectively controls perp's maximum debasement amount.
int256 minRolloverFeePerc;
/// @notice The slope of the linear rollover fee curve when (dr <= 1).
/// @dev This is represented as fixed point number with {DECIMALS} places.
/// For example, setting this to (0.1 / 13), would mean that the yearly perp debasement rate is capped at ~10%.
uint256 maxPerpDebasementPerc;
/// @notice The slope of the linear fee curve when (dr <= 1).
/// @dev This is represented as fixed point number with {DECIMALS} places.
/// Setting it to (1.0 / 13), would mean that it would take 1 year for dr to increase to 1.0.
/// Setting it to say (28 / 365), given a 28 day bond cycle it would take 1 year for dr to increase to 1.0.
/// (assuming no other changes to the system)
uint256 m1;
/// @notice The slope of the linear fee curve when (dr > 1).
uint256 perpDebasementSlope;
/// @notice The slope of the linear rollover fee curve when (dr > 1).
/// @dev This is represented as fixed point number with {DECIMALS} places.
/// Setting it to (1.0 / 13), would mean that it would take 1 year for dr to decrease to 1.0.
/// Setting it to say (28 / 365), given a 28 day bond cycle it would take 1 year for dr to decrease to 1.0.
/// (assuming no other changes to the system)
uint256 m2;
uint256 perpEnrichmentSlope;
}

/// @notice Parameters which control the perp rollover fee,
Expand Down Expand Up @@ -155,14 +157,15 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
vaultUnderlyingToPerpSwapFeePerc = ONE;
vaultPerpToUnderlyingSwapFeePerc = ONE;

// NOTE: With the current bond length of 28 days, rollover rate is annualized by dividing by: 365/28 ~= 13
perpRolloverFee.maxPerpDebasementPerc = ONE / (10 * 13); // 0.1/13 = 0.0077 (10% annualized)
perpRolloverFee.m1 = ONE / (3 * 13); // 0.025
perpRolloverFee.m2 = ONE / (3 * 13); // 0.025
// NOTE: With the current bond length of 28 days,
// rollover fee percentages are annualized by dividing by: 365/28 ~= 13
perpRolloverFee.minRolloverFeePerc = -int256(ONE) / 13; // 0.077 (-100% annualized)
perpRolloverFee.perpDebasementSlope = ONE / 13; // 0.077
perpRolloverFee.perpEnrichmentSlope = ONE / 13; // 0.077

targetSubscriptionRatio = (ONE * 133) / 100; // 1.33
deviationRatioBoundLower = (ONE * 75) / 100; // 0.75
deviationRatioBoundUpper = 2 * ONE; // 2.0
targetSubscriptionRatio = (ONE * 150) / 100; // 1.5
deviationRatioBoundLower = (ONE * 90) / 100; // 0.9
deviationRatioBoundUpper = 4 * ONE; // 4.0
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -272,17 +275,22 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
}

/// @inheritdoc IFeePolicy
function computePerpRolloverFeePerc(uint256 dr) external view override returns (int256) {
function computePerpRolloverFeePerc(uint256 dr) external view override returns (int256 rolloverFeePerc) {
RolloverFeeParams memory c = perpRolloverFee;
if (dr <= ONE) {
uint256 negPerpRate = MathUpgradeable.min(
perpRolloverFee.m1.mulDiv(ONE - dr, ONE),
perpRolloverFee.maxPerpDebasementPerc
);
return -1 * negPerpRate.toInt256();
// The cappedSr is the essentially the percentage of perp's collateral which can be rolled over
// given the vault's current liquidity level.
// Said simply, if cappedSr is 0.5 only 50% of the perp's collateral can be rolled over.
// If cappedSr is 1.0, all of perp's collateral can be rolled over.
uint256 cappedSr = MathUpgradeable.min(dr * targetSubscriptionRatio, ONE);
uint256 negRolloverFeePerc = cappedSr > 0
? (ONE - dr).mulDiv(c.perpDebasementSlope, cappedSr, MathUpgradeable.Rounding.Up)
: c.perpDebasementSlope;
rolloverFeePerc = -negRolloverFeePerc.toInt256();
} else {
uint256 perpRate = perpRolloverFee.m2.mulDiv(dr - ONE, ONE);
return perpRate.toInt256();
rolloverFeePerc = (dr - ONE).mulDiv(c.perpEnrichmentSlope, ONE).toInt256();
}
rolloverFeePerc = SignedMathUpgradeable.max(rolloverFeePerc, c.minRolloverFeePerc);
}

/// @inheritdoc IFeePolicy
Expand Down
2 changes: 1 addition & 1 deletion spot-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.9",
"ganache-cli": "^6.12.2",
"hardhat": "^2.19.4",
"hardhat": "^2.22.19",
"hardhat-gas-reporter": "^1.0.9",
"lodash": "^4.17.21",
"prettier": "^2.7.1",
Expand Down
76 changes: 40 additions & 36 deletions spot-contracts/test/FeePolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ describe("FeePolicy", function () {

describe("#init", function () {
it("should return the initial parameters", async function () {
expect(await feePolicy.targetSubscriptionRatio()).to.eq(toPerc("1.33"));
expect(await feePolicy.deviationRatioBoundLower()).to.eq(toPerc("0.75"));
expect(await feePolicy.deviationRatioBoundUpper()).to.eq(toPerc("2"));
expect(await feePolicy.targetSubscriptionRatio()).to.eq(toPerc("1.5"));
expect(await feePolicy.deviationRatioBoundLower()).to.eq(toPerc("0.9"));
expect(await feePolicy.deviationRatioBoundUpper()).to.eq(toPerc("4"));
const r = await feePolicy.perpRolloverFee();
expect(await r.minRolloverFeePerc).to.eq(toPerc("-0.07692307"));
expect(await r.perpDebasementSlope).to.eq(toPerc("0.07692307"));
expect(await r.perpEnrichmentSlope).to.eq(toPerc("0.07692307"));
});
it("should return owner", async function () {
expect(await feePolicy.owner()).to.eq(await deployer.getAddress());
Expand Down Expand Up @@ -63,7 +67,7 @@ describe("FeePolicy", function () {

describe("when triggered by owner", function () {
it("should update the target sr", async function () {
expect(await feePolicy.targetSubscriptionRatio()).to.eq(toPerc("1.33"));
expect(await feePolicy.targetSubscriptionRatio()).to.eq(toPerc("1.5"));
await feePolicy.connect(deployer).updateTargetSubscriptionRatio(toPerc("1.25"));
expect(await feePolicy.targetSubscriptionRatio()).to.eq(toPerc("1.25"));
});
Expand Down Expand Up @@ -97,8 +101,8 @@ describe("FeePolicy", function () {

describe("when triggered by owner", function () {
it("should update the target sr", async function () {
expect(await feePolicy.deviationRatioBoundLower()).to.eq(toPerc("0.75"));
expect(await feePolicy.deviationRatioBoundUpper()).to.eq(toPerc("2"));
expect(await feePolicy.deviationRatioBoundLower()).to.eq(toPerc("0.9"));
expect(await feePolicy.deviationRatioBoundUpper()).to.eq(toPerc("4"));
await feePolicy.connect(deployer).updateDeviationRatioBounds(toPerc("0.5"), toPerc("1.5"));
expect(await feePolicy.deviationRatioBoundLower()).to.eq(toPerc("0.5"));
expect(await feePolicy.deviationRatioBoundUpper()).to.eq(toPerc("1.5"));
Expand Down Expand Up @@ -165,9 +169,9 @@ describe("FeePolicy", function () {
it("should revert", async function () {
await expect(
feePolicy.connect(otherUser).updatePerpRolloverFees({
maxPerpDebasementPerc: toPerc("0.01"),
m1: toPerc("0.01"),
m2: toPerc("0.01"),
minRolloverFeePerc: toPerc("-0.01"),
perpDebasementSlope: toPerc("0.01"),
perpEnrichmentSlope: toPerc("0.01"),
}),
).to.be.revertedWith("Ownable: caller is not the owner");
});
Expand All @@ -176,14 +180,14 @@ describe("FeePolicy", function () {
describe("when triggered by owner", function () {
it("should update parameters", async function () {
await feePolicy.connect(deployer).updatePerpRolloverFees({
maxPerpDebasementPerc: toPerc("0.01"),
m1: toPerc("0.02"),
m2: toPerc("0.03"),
minRolloverFeePerc: toPerc("-0.01"),
perpDebasementSlope: toPerc("0.02"),
perpEnrichmentSlope: toPerc("0.03"),
});
const p = await feePolicy.perpRolloverFee();
expect(p.maxPerpDebasementPerc).to.eq(toPerc("0.01"));
expect(p.m1).to.eq(toPerc("0.02"));
expect(p.m2).to.eq(toPerc("0.03"));
expect(p.minRolloverFeePerc).to.eq(toPerc("-0.01"));
expect(p.perpDebasementSlope).to.eq(toPerc("0.02"));
expect(p.perpEnrichmentSlope).to.eq(toPerc("0.03"));
});
});
});
Expand Down Expand Up @@ -304,9 +308,9 @@ describe("FeePolicy", function () {
await feePolicy.updatePerpMintFees(toPerc("0.025"));
await feePolicy.updatePerpBurnFees(toPerc("0.035"));
await feePolicy.updatePerpRolloverFees({
maxPerpDebasementPerc: toPerc("0.1"),
m1: toPerc("0.3"),
m2: toPerc("0.6"),
minRolloverFeePerc: toPerc("-0.8"),
perpDebasementSlope: toPerc("1"),
perpEnrichmentSlope: toPerc("2"),
});
await feePolicy.updateVaultUnderlyingToPerpSwapFeePerc(toPerc("0.1"));
await feePolicy.updateVaultPerpToUnderlyingSwapFeePerc(toPerc("0.15"));
Expand Down Expand Up @@ -447,25 +451,25 @@ describe("FeePolicy", function () {

describe("rollover fee", function () {
it("should compute fees as expected", async function () {
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0"))).to.eq(toPerc("-0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.01"))).to.eq(toPerc("-0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.25"))).to.eq(toPerc("-0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.5"))).to.eq(toPerc("-0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.66"))).to.eq(toPerc("-0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.7"))).to.eq(toPerc("-0.09"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.8"))).to.eq(toPerc("-0.06"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.9"))).to.eq(toPerc("-0.03"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.99"))).to.eq(toPerc("-0.003"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0"))).to.eq(toPerc("-0.8"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.01"))).to.eq(toPerc("-0.8"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.25"))).to.eq(toPerc("-0.75"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.5"))).to.eq(toPerc("-0.5"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.66"))).to.eq(toPerc("-0.34"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.7"))).to.eq(toPerc("-0.3"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.8"))).to.eq(toPerc("-0.2"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.9"))).to.eq(toPerc("-0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.99"))).to.eq(toPerc("-0.01"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1"))).to.eq("0");
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.01"))).to.eq(toPerc("0.006"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.05"))).to.eq(toPerc("0.03"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.1"))).to.eq(toPerc("0.06"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.25"))).to.eq(toPerc("0.15"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.5"))).to.eq(toPerc("0.3"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.75"))).to.eq(toPerc("0.45"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("2"))).to.eq(toPerc("0.6"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("5"))).to.eq(toPerc("2.4"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("10"))).to.eq(toPerc("5.4"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.01"))).to.eq(toPerc("0.02"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.05"))).to.eq(toPerc("0.1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.1"))).to.eq(toPerc("0.2"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.25"))).to.eq(toPerc("0.5"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.5"))).to.eq(toPerc("1"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.75"))).to.eq(toPerc("1.5"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("2"))).to.eq(toPerc("2"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("5"))).to.eq(toPerc("8"));
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("10"))).to.eq(toPerc("18"));
});
});
});
Expand Down
Loading