Skip to content

Commit 86ca566

Browse files
committed
fee curve update
1 parent a8ae0ae commit 86ca566

File tree

6 files changed

+137
-95
lines changed

6 files changed

+137
-95
lines changed

spot-contracts/contracts/FeePolicy.sol

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8.20;
33

44
import { IFeePolicy } from "./_interfaces/IFeePolicy.sol";
55
import { SubscriptionParams } from "./_interfaces/CommonTypes.sol";
6-
import { InvalidPerc, InvalidTargetSRBounds, InvalidDRBounds, InvalidSigmoidAsymptotes } from "./_interfaces/ProtocolErrors.sol";
6+
import { InvalidPerc, InvalidTargetSRBounds, InvalidDRBounds } from "./_interfaces/ProtocolErrors.sol";
77

88
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
99
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
@@ -54,6 +54,7 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
5454
// Libraries
5555
using MathUpgradeable for uint256;
5656
using SafeCastUpgradeable for uint256;
57+
using SafeCastUpgradeable for int256;
5758

5859
// Replicating value used here:
5960
// https://github.com/buttonwood-protocol/tranche/blob/main/contracts/BondController.sol
@@ -67,10 +68,6 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
6768
/// @notice Fixed point representation of 1.0 or 100%.
6869
uint256 public constant ONE = (1 * 10 ** DECIMALS);
6970

70-
/// @notice Sigmoid asymptote bound.
71-
/// @dev Set to 0.05 or 5%, i.e) the rollover fee can be at most 5% on either direction.
72-
uint256 public constant SIGMOID_BOUND = ONE / 20;
73-
7471
/// @notice Target subscription ratio lower bound, 0.75 or 75%.
7572
uint256 public constant TARGET_SR_LOWER_BOUND = (ONE * 75) / 100;
7673

@@ -100,17 +97,18 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
10097
/// @notice The percentage fee charged on burning perp tokens.
10198
uint256 public perpBurnFeePerc;
10299

103-
struct RolloverFeeSigmoidParams {
104-
/// @notice Lower asymptote
105-
int256 lower;
106-
/// @notice Upper asymptote
107-
int256 upper;
108-
/// @notice sigmoid slope
100+
struct RolloverFeeParams {
101+
/// @notice The maximum rate perp pays the vault for rollovers.
102+
int256 perpRateMax;
103+
/// @notice The maximum rate vault pays the perp for rollovers.
104+
int256 vaultRateMax;
105+
/// @notice Sigmoid slope
109106
int256 growth;
110107
}
111108

112-
/// @notice Parameters which control the asymptotes and the slope of the perp token's rollover fee.
113-
RolloverFeeSigmoidParams public perpRolloverFee;
109+
/// @notice Parameters which control the perp rollover fee,
110+
/// i.e) the funding rate for holding perps.
111+
RolloverFeeParams public perpRolloverFee;
114112

115113
//-----------------------------------------------------------------------------
116114

@@ -151,9 +149,9 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
151149
vaultPerpToUnderlyingSwapFeePerc = ONE;
152150

153151
// NOTE: With the current bond length of 28 days, rollover rate is annualized by dividing by: 365/28 ~= 13
154-
perpRolloverFee.lower = -int256(ONE) / (30 * 13); // -0.033/13 = -0.00253 (3.3% annualized)
155-
perpRolloverFee.upper = int256(ONE) / (10 * 13); // 0.1/13 = 0.00769 (10% annualized)
156-
perpRolloverFee.growth = 5 * int256(ONE); // 5.0
152+
perpRolloverFee.perpRateMax = int256(ONE) / (10 * 13); // 0.1/13 = 0.0077 (10% annualized)
153+
perpRolloverFee.vaultRateMax = int256(ONE) / (20 * 13); // 0.05/13 = 0.00385 (5% annualized)
154+
perpRolloverFee.growth = 25 * int256(ONE); // 25.0
157155

158156
targetSubscriptionRatio = (ONE * 133) / 100; // 1.33
159157
deviationRatioBoundLower = (ONE * 75) / 100; // 0.75
@@ -208,15 +206,13 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
208206

209207
/// @notice Update the parameters determining the slope and asymptotes of the sigmoid fee curve.
210208
/// @param p Lower, Upper and Growth sigmoid paramters are fixed point numbers with {DECIMALS} places.
211-
function updatePerpRolloverFees(RolloverFeeSigmoidParams calldata p) external onlyOwner {
209+
function updatePerpRolloverFees(RolloverFeeParams calldata p) external onlyOwner {
212210
// If the bond duration is 28 days and 13 rollovers happen per year,
213211
// perp can be inflated or enriched up to ~65% annually.
214-
if (p.lower < -int256(SIGMOID_BOUND) || p.upper > int256(SIGMOID_BOUND) || p.lower > p.upper) {
215-
revert InvalidSigmoidAsymptotes();
212+
if (p.perpRateMax < 0 || p.vaultRateMax < 0) {
213+
revert InvalidPerc();
216214
}
217-
perpRolloverFee.lower = p.lower;
218-
perpRolloverFee.upper = p.upper;
219-
perpRolloverFee.growth = p.growth;
215+
perpRolloverFee = p;
220216
}
221217

222218
/// @notice Updates the vault mint fee parameters.
@@ -273,15 +269,33 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
273269
}
274270

275271
/// @inheritdoc IFeePolicy
276-
function computePerpRolloverFeePerc(uint256 dr) external view override returns (int256) {
277-
return
278-
Sigmoid.compute(
279-
dr.toInt256(),
280-
perpRolloverFee.lower,
281-
perpRolloverFee.upper,
282-
perpRolloverFee.growth,
283-
ONE.toInt256()
284-
);
272+
function computePerpRolloverFeePerc(uint256 dr, uint256 seniorTR) external view override returns (int256) {
273+
if (dr <= ONE) {
274+
return
275+
Sigmoid.compute(
276+
dr.toInt256(),
277+
-perpRolloverFee.perpRateMax,
278+
perpRolloverFee.perpRateMax,
279+
perpRolloverFee.growth,
280+
ONE.toInt256()
281+
);
282+
} else {
283+
uint256 vaultRate = Sigmoid
284+
.compute(
285+
dr.toInt256(),
286+
-perpRolloverFee.vaultRateMax,
287+
perpRolloverFee.vaultRateMax,
288+
perpRolloverFee.growth,
289+
ONE.toInt256()
290+
)
291+
.toUint256();
292+
return
293+
vaultRate
294+
.mulDiv(dr, ONE)
295+
.mulDiv(targetSubscriptionRatio, ONE)
296+
.mulDiv(TRANCHE_RATIO_GRANULARITY - seniorTR, seniorTR)
297+
.toInt256();
298+
}
285299
}
286300

287301
/// @inheritdoc IFeePolicy

spot-contracts/contracts/PerpetualTranche.sol

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -792,14 +792,12 @@ contract PerpetualTranche is
792792
// The rollover fees are settled by, adjusting the exchange rate
793793
// between `trancheInAmt` and `tokenOutAmt`.
794794
//
795+
uint256 seniorTR = _depositBond.getSeniorTrancheRatio();
795796
int256 feePerc = feePolicy.computePerpRolloverFeePerc(
796797
feePolicy.computeDeviationRatio(
797-
SubscriptionParams({
798-
perpTVL: _reserveValue(),
799-
vaultTVL: vault.getTVL(),
800-
seniorTR: _depositBond.getSeniorTrancheRatio()
801-
})
802-
)
798+
SubscriptionParams({ perpTVL: _reserveValue(), vaultTVL: vault.getTVL(), seniorTR: seniorTR })
799+
),
800+
seniorTR
803801
);
804802
//-----------------------------------------------------------------------------
805803

spot-contracts/contracts/_interfaces/IFeePolicy.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface IFeePolicy {
1313
function computePerpBurnFeePerc() external view returns (uint256);
1414

1515
/// @param dr The current system deviation ratio.
16+
/// @param seniorTR The tranche ratio of senior tranches accepted to mint perps.
1617
/// @return The applied exchange rate adjustment between tranches into perp and
1718
/// tokens out of perp during a rollover,
1819
/// as a fixed-point number with {DECIMALS} decimal places.
@@ -22,7 +23,7 @@ interface IFeePolicy {
2223
/// example) 100 tranchesIn for 99 tranchesOut; i.e) perp enrichment
2324
/// - A fee of -1%, implies the exchange rate is adjusted in favor of tranchesOut.
2425
/// example) 99 tranchesIn for 100 tranchesOut
25-
function computePerpRolloverFeePerc(uint256 dr) external view returns (int256);
26+
function computePerpRolloverFeePerc(uint256 dr, uint256 seniorTR) external view returns (int256);
2627

2728
/// @return The percentage of the mint vault note amount to be charged as fees,
2829
/// as a fixed-point number with {DECIMALS} decimal places.

spot-contracts/contracts/_interfaces/ProtocolErrors.sol

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,3 @@ error InvalidTargetSRBounds();
7575

7676
/// @notice Expected deviation ratio bounds to be valid.
7777
error InvalidDRBounds();
78-
79-
/// @notice Expected sigmoid asymptotes to be within defined bounds.
80-
error InvalidSigmoidAsymptotes();

spot-contracts/test/FeePolicy.ts

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ describe("FeePolicy", function () {
165165
it("should revert", async function () {
166166
await expect(
167167
feePolicy.connect(otherUser).updatePerpRolloverFees({
168-
lower: toPerc("-0.01"),
169-
upper: toPerc("0.01"),
168+
perpRateMax: toPerc("0.01"),
169+
vaultRateMax: toPerc("0.01"),
170170
growth: toPerc("3"),
171171
}),
172172
).to.be.revertedWith("Ownable: caller is not the owner");
@@ -177,48 +177,34 @@ describe("FeePolicy", function () {
177177
it("should revert", async function () {
178178
await expect(
179179
feePolicy.connect(deployer).updatePerpRolloverFees({
180-
lower: toPerc("-0.051"),
181-
upper: toPerc("0.01"),
180+
perpRateMax: toPerc("-0.05"),
181+
vaultRateMax: toPerc("0.01"),
182182
growth: toPerc("3"),
183183
}),
184-
).to.be.revertedWithCustomError(feePolicy, "InvalidSigmoidAsymptotes");
185-
});
186-
it("should revert", async function () {
187-
await expect(
188-
feePolicy.connect(deployer).updatePerpRolloverFees({
189-
lower: toPerc("-0.01"),
190-
upper: toPerc("0.051"),
191-
growth: toPerc("3"),
192-
}),
193-
).to.be.revertedWithCustomError(feePolicy, "InvalidSigmoidAsymptotes");
184+
).to.be.revertedWithCustomError(feePolicy, "InvalidPerc");
194185
});
195-
196186
it("should revert", async function () {
197187
await expect(
198188
feePolicy.connect(deployer).updatePerpRolloverFees({
199-
lower: toPerc("0.02"),
200-
upper: toPerc("0.01"),
189+
perpRateMax: toPerc("0.01"),
190+
vaultRateMax: toPerc("-0.05"),
201191
growth: toPerc("3"),
202192
}),
203-
).to.be.revertedWithCustomError(feePolicy, "InvalidSigmoidAsymptotes");
193+
).to.be.revertedWithCustomError(feePolicy, "InvalidPerc");
204194
});
205195
});
206196

207197
describe("when triggered by owner", function () {
208198
it("should update parameters", async function () {
209-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1"))).to.eq(0);
210-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("10"))).to.eq(toPerc("0.00769230"));
211-
expect(await feePolicy.computePerpRolloverFeePerc("0")).to.eq(toPerc("-0.00245837"));
212-
213199
await feePolicy.connect(deployer).updatePerpRolloverFees({
214-
lower: toPerc("-0.009"),
215-
upper: toPerc("0.009"),
216-
growth: toPerc("3"),
200+
perpRateMax: toPerc("0.009"),
201+
vaultRateMax: toPerc("0.01"),
202+
growth: toPerc("7"),
217203
});
218-
219-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1"))).to.eq(0);
220-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("10"))).to.eq(toPerc("0.009"));
221-
expect(await feePolicy.computePerpRolloverFeePerc("0")).to.eq(toPerc("-0.007"));
204+
const p = await feePolicy.perpRolloverFee();
205+
expect(p.perpRateMax).to.eq(toPerc("0.009"));
206+
expect(p.vaultRateMax).to.eq(toPerc("0.01"));
207+
expect(p.growth).to.eq(toPerc("7"));
222208
});
223209
});
224210
});
@@ -339,9 +325,9 @@ describe("FeePolicy", function () {
339325
await feePolicy.updatePerpMintFees(toPerc("0.025"));
340326
await feePolicy.updatePerpBurnFees(toPerc("0.035"));
341327
await feePolicy.updatePerpRolloverFees({
342-
lower: toPerc("-0.00253"),
343-
upper: toPerc("0.00769"),
344-
growth: toPerc("5"),
328+
perpRateMax: toPerc("0.1"),
329+
vaultRateMax: toPerc("0.05"),
330+
growth: toPerc("25"),
345331
});
346332
await feePolicy.updateVaultUnderlyingToPerpSwapFeePerc(toPerc("0.1"));
347333
await feePolicy.updateVaultPerpToUnderlyingSwapFeePerc(toPerc("0.15"));
@@ -482,21 +468,28 @@ describe("FeePolicy", function () {
482468

483469
describe("rollover fee", function () {
484470
it("should compute fees as expected", async function () {
485-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.01"))).to.eq(toPerc("-0.00242144"));
486-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.25"))).to.eq(toPerc("-0.00228606"));
487-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.5"))).to.eq(toPerc("-0.00196829"));
488-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.75"))).to.eq(toPerc("-0.00128809"));
489-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.9"))).to.eq(toPerc("-0.00060117"));
490-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.99"))).to.eq(toPerc("-0.00004101"));
491-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1"))).to.eq("0");
492-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.01"))).to.eq(toPerc("0.00004146"));
493-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.05"))).to.eq(toPerc("0.00034407"));
494-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.1"))).to.eq(toPerc("0.00071519"));
495-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.25"))).to.eq(toPerc("0.00195646"));
496-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.5"))).to.eq(toPerc("0.00411794"));
497-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.75"))).to.eq(toPerc("0.00580663"));
498-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("2"))).to.eq(toPerc("0.00680345"));
499-
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("5"))).to.eq(toPerc("0.00768997"));
471+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.01"), 333)).to.eq(toPerc("-0.1"));
472+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.25"), 333)).to.eq(toPerc("-0.09999955"));
473+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.5"), 333)).to.eq(toPerc("-0.09996548"));
474+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.75"), 333)).to.eq(toPerc("-0.09740628"));
475+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.9"), 333)).to.eq(toPerc("-0.06995578"));
476+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.99"), 333)).to.eq(toPerc("-0.00864273"));
477+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1"), 333)).to.eq("0");
478+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.01"), 333)).to.eq(toPerc("0.01162717"));
479+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.05"), 333)).to.eq(toPerc("0.05706359"));
480+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.1"), 333)).to.eq(toPerc("0.10249891"));
481+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.25"), 333)).to.eq(toPerc("0.16218105"));
482+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.5"), 333)).to.eq(toPerc("0.19973050"));
483+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.75"), 333)).to.eq(toPerc("0.23309837"));
484+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("2"), 333)).to.eq(toPerc("0.26639933"));
485+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("5"), 333)).to.eq(toPerc("0.66599849"));
486+
487+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.9"), 500)).to.eq(toPerc("-0.06995578"));
488+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("0.99"), 500)).to.eq(toPerc("-0.00864273"));
489+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1"), 500)).to.eq("0");
490+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.01"), 500)).to.eq(toPerc("0.00580487"));
491+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.05"), 500)).to.eq(toPerc("0.02848902"));
492+
expect(await feePolicy.computePerpRolloverFeePerc(toPerc("1.1"), 500)).to.eq(toPerc("0.05117262"));
500493
});
501494
});
502495
});

spot-vaults/tasks/upgrade.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@ import { TaskArguments } from "hardhat/types";
44
import { sleep } from "./tools";
55

66
task("validate_upgrade")
7-
.addPositionalParam("factory", "the name of the factory", undefined, types.string, false)
8-
.addPositionalParam("address", "the address of the deployed proxy contract", undefined, types.string, false)
7+
.addPositionalParam(
8+
"factory",
9+
"the name of the factory",
10+
undefined,
11+
types.string,
12+
false,
13+
)
14+
.addPositionalParam(
15+
"address",
16+
"the address of the deployed proxy contract",
17+
undefined,
18+
types.string,
19+
false,
20+
)
921
.setAction(async function (args: TaskArguments, hre) {
1022
const { factory, address } = args;
1123
const Factory = await hre.ethers.getContractFactory(factory);
@@ -23,8 +35,20 @@ task("validate_upgrade")
2335
});
2436

2537
task("prepare_upgrade")
26-
.addPositionalParam("factory", "the name of the factory", undefined, types.string, false)
27-
.addPositionalParam("address", "the address of the deployed proxy contract", undefined, types.string, false)
38+
.addPositionalParam(
39+
"factory",
40+
"the name of the factory",
41+
undefined,
42+
types.string,
43+
false,
44+
)
45+
.addPositionalParam(
46+
"address",
47+
"the address of the deployed proxy contract",
48+
undefined,
49+
types.string,
50+
false,
51+
)
2852
.addParam("fromIdx", "the index of sender", 0, types.int)
2953
.setAction(async function (args: TaskArguments, hre) {
3054
const { factory, address } = args;
@@ -52,8 +76,20 @@ task("prepare_upgrade")
5276
});
5377

5478
task("upgrade:testnet")
55-
.addPositionalParam("factory", "the name of the factory", undefined, types.string, false)
56-
.addPositionalParam("address", "the address of the deployed proxy contract", undefined, types.string, false)
79+
.addPositionalParam(
80+
"factory",
81+
"the name of the factory",
82+
undefined,
83+
types.string,
84+
false,
85+
)
86+
.addPositionalParam(
87+
"address",
88+
"the address of the deployed proxy contract",
89+
undefined,
90+
types.string,
91+
false,
92+
)
5793
.addParam("fromIdx", "the index of sender", 0, types.int)
5894
.setAction(async function (args: TaskArguments, hre) {
5995
const signer = (await hre.ethers.getSigners())[args.fromIdx];
@@ -64,7 +100,10 @@ task("upgrade:testnet")
64100
const Factory = await hre.ethers.getContractFactory(factory);
65101

66102
console.log("Proxy", address);
67-
console.log("Current implementation", await getImplementationAddress(hre.ethers.provider, address));
103+
console.log(
104+
"Current implementation",
105+
await getImplementationAddress(hre.ethers.provider, address),
106+
);
68107

69108
const impl = await hre.upgrades.upgradeProxy(address, Factory, {
70109
unsafeAllowRenames: true,

0 commit comments

Comments
 (0)