Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b966a8e
DR based flash swap fees
aalavandhan Mar 4, 2025
9ddef4d
comment updates
aalavandhan Mar 6, 2025
54877f0
updated unit tests to use ethers v6, using custom mocking library
aalavandhan Mar 5, 2025
a7a5f41
updated tasks and deps
aalavandhan Mar 6, 2025
e8a35ed
updated perp mint fees to be paid to the vault
aalavandhan Mar 8, 2025
eff36d7
removed perp share of fees from flash swaps, all fees go to the vault
aalavandhan Mar 8, 2025
433263f
added configurable dr soft bounds (curve cutoff point) to flash swap …
aalavandhan Mar 8, 2025
28db923
updated unit tests
aalavandhan Mar 8, 2025
293c25d
removed rollover fees from perp
aalavandhan Mar 9, 2025
3d11ca3
Daily Rebalance
aalavandhan Mar 11, 2025
061aaf9
updated unit tests
aalavandhan Mar 11, 2025
e11e203
constant rate enrichment/debasement
aalavandhan Mar 12, 2025
0459829
mint2, redeem2
aalavandhan Mar 12, 2025
2c6b1f2
added linked library to limit contract size
aalavandhan Mar 14, 2025
5a28255
unit tests
aalavandhan Mar 14, 2025
272be4a
melding perps immediately after rebalance
aalavandhan Mar 14, 2025
8b7a29d
Apply suggestions from code review
aalavandhan Mar 24, 2025
f1ed255
Update spot-contracts/contracts/FeePolicy.sol
aalavandhan Mar 24, 2025
0fc7733
code review fixes
aalavandhan Mar 24, 2025
70ea8d1
code review fixes v2
aalavandhan Mar 27, 2025
654d290
code review fixes v3
aalavandhan Mar 27, 2025
68ad680
code review v4
aalavandhan Mar 31, 2025
8e9f016
during rebalance, vault pays perps by transferring tranches into perp…
aalavandhan Mar 31, 2025
e461e1e
rebalance eql
aalavandhan Mar 31, 2025
76f3831
configurable rebalance freq
aalavandhan Apr 1, 2025
f9fdff0
code review v5
aalavandhan Apr 3, 2025
3ef2756
Apply suggestions from code review
aalavandhan Apr 9, 2025
b23ad28
code review fixes
aalavandhan Apr 16, 2025
2403fb5
Code review fixes
aalavandhan Apr 17, 2025
2c97ec7
Merge branch 'rollover-fee-removal' into rebal-param-update
aalavandhan Apr 18, 2025
4a5ce29
Merge pull request #249 from ampleforth/rebal-param-update
aalavandhan Apr 18, 2025
5bdecd3
Merge branch 'rollover-fee-removal' into pair-ops
aalavandhan Apr 18, 2025
6779862
Merge pull request #247 from ampleforth/pair-ops
aalavandhan Apr 18, 2025
abc1164
Merge pull request #246 from ampleforth/rollover-fee-removal
aalavandhan Apr 18, 2025
eed0d39
Merge pull request #243 from ampleforth/fee-restructure
aalavandhan Apr 18, 2025
e6c9aea
Merge pull request #242 from ampleforth/ethers-update
aalavandhan Apr 18, 2025
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
126 changes: 75 additions & 51 deletions spot-contracts/contracts/FeePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
pragma solidity ^0.8.20;

import { IFeePolicy } from "./_interfaces/IFeePolicy.sol";
import { SubscriptionParams } from "./_interfaces/CommonTypes.sol";
import { SubscriptionParams, Range, Line } from "./_interfaces/CommonTypes.sol";
import { InvalidPerc, InvalidTargetSRBounds, InvalidDRBounds } from "./_interfaces/ProtocolErrors.sol";

import { LineHelpers } from "./_utils/LineHelpers.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";
/**
*
* @title FeePolicy
*
* @notice This contract determines fees for interacting with the perp and vault systems.
Expand Down Expand Up @@ -78,13 +80,9 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// Adds a safety buffer to ensure that rollovers are better sustained.
uint256 public targetSubscriptionRatio;

/// @notice The lower bound of deviation ratio, below which some operations (which decrease the dr) are disabled.
uint256 public deviationRatioBoundLower;

/// @notice The upper bound of deviation ratio, above which some operations (which increase the dr) are disabled.
uint256 public deviationRatioBoundUpper;

//-----------------------------------------------------------------------------
/// @notice The lower and upper deviation ratio bounds outside which
/// some operations (like flash swaps) which move dr away from 1.0 are disabled.
Range public drHardBounds;
Comment thread
brandoniles marked this conversation as resolved.
Outdated

//-----------------------------------------------------------------------------
// Perp fee parameters
Expand Down Expand Up @@ -119,8 +117,6 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// i.e) the funding rate for holding perps.
RolloverFeeParams public perpRolloverFee;

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Vault fee parameters

Expand All @@ -130,11 +126,11 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// @notice The percentage fee charged on burning vault notes.
uint256 public vaultBurnFeePerc;

/// @notice The percentage fee charged by the vault to swap underlying tokens for perp tokens.
uint256 public vaultUnderlyingToPerpSwapFeePerc;
/// @notice Lower and upper fee percentages for flash minting.
Range public flashMintFeePercs;

/// @notice The percentage fee charged by the vault to swap perp tokens for underlying tokens.
uint256 public vaultPerpToUnderlyingSwapFeePerc;
/// @notice Lower and upper fee percentages for flash redemption.
Range public flashRedeemFeePercs;

//-----------------------------------------------------------------------------

Expand All @@ -147,25 +143,28 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
function init() external initializer {
__Ownable_init();

// initializing mint/burn fees to zero
targetSubscriptionRatio = (ONE * 150) / 100; // 1.5
drHardBounds = Range({
lower: (ONE * 90) / 100, // 0.9
upper: 4 * ONE // 4.0
});

// initializing fees
perpMintFeePerc = 0;
perpBurnFeePerc = 0;
vaultMintFeePerc = 0;
vaultBurnFeePerc = 0;

// initializing swap fees to 100%, to disable swapping initially
vaultUnderlyingToPerpSwapFeePerc = ONE;
vaultPerpToUnderlyingSwapFeePerc = ONE;

// 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 * 150) / 100; // 1.5
deviationRatioBoundLower = (ONE * 90) / 100; // 0.9
deviationRatioBoundUpper = 4 * ONE; // 4.0
vaultMintFeePerc = 0;
vaultBurnFeePerc = 0;

// initializing swap fees to 100%, to disable swapping initially
flashMintFeePercs = Range({ lower: ONE, upper: ONE });
flashRedeemFeePercs = Range({ lower: ONE, upper: ONE });
}

//-----------------------------------------------------------------------------
Expand All @@ -181,17 +180,12 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
}

/// @notice Updates the deviation ratio bounds.
/// @param deviationRatioBoundLower_ The new lower deviation ratio bound as fixed point number with {DECIMALS} places.
/// @param deviationRatioBoundUpper_ The new upper deviation ratio bound as fixed point number with {DECIMALS} places.
function updateDeviationRatioBounds(
uint256 deviationRatioBoundLower_,
uint256 deviationRatioBoundUpper_
) external onlyOwner {
if (deviationRatioBoundLower_ > ONE || deviationRatioBoundUpper_ < ONE) {
/// @param drHardBounds_ The new hard lower and upper deviation ratio bound as fixed point number with {DECIMALS} places.
function updateDRHardBounds(Range memory drHardBounds_) external onlyOwner {
if (drHardBounds_.lower > ONE || drHardBounds_.upper < ONE) {
revert InvalidDRBounds();
}
deviationRatioBoundLower = deviationRatioBoundLower_;
deviationRatioBoundUpper = deviationRatioBoundUpper_;
drHardBounds = drHardBounds_;
}

/// @notice Updates the perp mint fee parameters.
Expand Down Expand Up @@ -241,22 +235,20 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
vaultBurnFeePerc = vaultBurnFeePerc_;
}

/// @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 {
if (feePerc > ONE) {
/// @notice Updates the vault flash swap fee percentages.
/// @param flashMintFeePercs_ The lower and upper flash mint fee percentages as a fixed point numbers with {DECIMALS} places.
/// @param flashRedeemFeePercs_ The lower and upper flash redemption fee percentages as a fixed point numbers with {DECIMALS} places.
function updateFlashFees(Range memory flashMintFeePercs_, Range memory flashRedeemFeePercs_) external onlyOwner {
if (
flashMintFeePercs_.lower > ONE ||
flashMintFeePercs_.upper > ONE ||
flashRedeemFeePercs_.lower > ONE ||
flashRedeemFeePercs_.upper > 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 {
if (feePerc > ONE) {
revert InvalidPerc();
}
vaultPerpToUnderlyingSwapFeePerc = feePerc;
flashMintFeePercs = flashMintFeePercs_;
flashRedeemFeePercs = flashRedeemFeePercs_;
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -307,23 +299,55 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// @inheritdoc IFeePolicy
/// @dev Swapping by minting perps reduces system dr, i.e) drPost < drPre.
function computeUnderlyingToPerpVaultSwapFeePerc(
uint256 /*drPre*/,
uint256 drPre,
uint256 drPost
) external view override returns (uint256) {
// When the after op deviation ratio is below the bound,
// swapping is disabled. (fees are set to 100%)
return (drPost < deviationRatioBoundLower ? ONE : vaultUnderlyingToPerpSwapFeePerc);
if (drPost <= drHardBounds.lower) {
return ONE;
}
return
LineHelpers
.computePiecewiseAvgY(
Line({ x1: drHardBounds.lower, y1: flashMintFeePercs.upper, x2: ONE, y2: flashMintFeePercs.lower }),
Line({ x1: ONE, y1: flashMintFeePercs.lower, x2: drHardBounds.upper, y2: flashMintFeePercs.lower }),
Comment thread
brandoniles marked this conversation as resolved.
Outdated
Range({ lower: drPost, upper: drPre }),
ONE
)
.toUint256();
Comment thread
brandoniles marked this conversation as resolved.
}

/// @inheritdoc IFeePolicy
/// @dev Swapping by burning perps increases system dr, i.e) drPost > drPre.
function computePerpToUnderlyingVaultSwapFeePerc(
uint256 /*drPre*/,
uint256 drPre,
uint256 drPost
) external view override returns (uint256) {
// When the after op deviation ratio is above the bound,
// swapping is disabled. (fees are set to 100%)
return (drPost > deviationRatioBoundUpper ? ONE : vaultPerpToUnderlyingSwapFeePerc);
if (drPost >= drHardBounds.upper) {
return ONE;
}
return
LineHelpers
.computePiecewiseAvgY(
Line({
x1: drHardBounds.lower,
y1: flashRedeemFeePercs.lower,
x2: ONE,
y2: flashRedeemFeePercs.lower
}),
Line({
x1: ONE,
y1: flashRedeemFeePercs.lower,
x2: drHardBounds.upper,
y2: flashRedeemFeePercs.upper
}),
Range({ lower: drPre, upper: drPost }),
ONE
)
.toUint256();
}

/// @inheritdoc IFeePolicy
Expand Down
20 changes: 20 additions & 0 deletions spot-contracts/contracts/_interfaces/CommonTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,23 @@ struct RolloverData {
/// @notice The amount of trancheIn tokens rolled in.
uint256 trancheInAmt;
}

/// @notice A data structure to define a numeric Range.
struct Range {
// @dev Lower bound of the range.
uint256 lower;
// @dev Upper bound of the range.
uint256 upper;
}

/// @notice A data structure to define a geometric Line with two points.
struct Line {
// @dev x-coordinate of the first point.
uint256 x1;
// @dev y-coordinate of the first point.
uint256 y1;
// @dev x-coordinate of the second point.
uint256 x2;
// @dev y-coordinate of the second point.
uint256 y2;
}
3 changes: 3 additions & 0 deletions spot-contracts/contracts/_interfaces/ProtocolErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ error OutOfBounds();
/// @notice Expected the number of reserve assets to be under the limit.
error ReserveCountOverLimit();

/// @notice Expected range to be non-decreasing.
error InvalidRange();

//-------------------------------------------------------------------------
// Perp

Expand Down
20 changes: 20 additions & 0 deletions spot-contracts/contracts/_test/LineHelpersTester.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import { LineHelpers } from "../_utils/LineHelpers.sol";
import { Line, Range } from "../_interfaces/CommonTypes.sol";

contract LineHelpersTester {
function computePiecewiseAvgY(
Line memory fn1,
Line memory fn2,
Range memory xRange,
uint256 xBreakPt
) public pure returns (int256) {
return LineHelpers.computePiecewiseAvgY(fn1, fn2, xRange, xBreakPt);
}

function avgY(Line memory fn, uint256 xL, uint256 xU) public pure returns (int256) {
return LineHelpers.avgY(fn, xL, xU);
}
}
85 changes: 85 additions & 0 deletions spot-contracts/contracts/_utils/LineHelpers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import { Line, Range } from "../_interfaces/CommonTypes.sol";
import { InvalidRange } from "../_interfaces/ProtocolErrors.sol";

/**
* @title LineHelpers
* @notice Provides helper functions for working with linear functions and computing piecewise averages.
*/
library LineHelpers {
using MathUpgradeable for uint256;
using SafeCastUpgradeable for uint256;
using SafeCastUpgradeable for int256;

/**
* @notice Computes the weighted average y-value over a specified x-range for a piecewise linear function.
* @dev This function considers two linear segments—`fn1` for x-values below the breakpoint (`xBreakPt`)
* and `fn2` for x-values above or equal to the breakpoint. If the entire x-range lies on one side of
* `xBreakPt`, it returns the average y-value computed over that range using the appropriate function.
* If the x-range spans `xBreakPt`, it computes a weighted average of the two sub-ranges, weighted by their lengths.
* @param fn1 The linear function used for x-values below `xBreakPt`.
* @param fn2 The linear function used for x-values above or equal to `xBreakPt`.
* @param xRange The x-range over which to compute the average.
* @param xBreakPt The x-coordinate where the piecewise function transitions from `fn1` to `fn2`.
* @return yVal The computed weighted average y-value over the x-range.
*/
function computePiecewiseAvgY(
Line memory fn1,
Line memory fn2,
Range memory xRange,
uint256 xBreakPt
) internal pure returns (int256 yVal) {
if (xRange.lower > xRange.upper) {
revert InvalidRange();
}

if (xRange.upper <= xBreakPt) {
// Entire range is below the breakpoint.
yVal = avgY(fn1, xRange.lower, xRange.upper);
} else if (xRange.lower >= xBreakPt) {
// Entire range is above or equal to the breakpoint.
yVal = avgY(fn2, xRange.lower, xRange.upper);
} else {
// Range spans the breakpoint, so compute weighted average of both segments.
uint256 len1 = xBreakPt - xRange.lower;
uint256 len2 = xRange.upper - xBreakPt;
int256 avg1 = avgY(fn1, xRange.lower, xBreakPt);
int256 avg2 = avgY(fn2, xBreakPt, xRange.upper);
yVal = (avg1 * int256(len1) + avg2 * int256(len2)) / int256(xRange.upper - xRange.lower);
}
}

/**
* @notice Computes the average y-value of a linear function over the interval [xL, xU].
* @dev For a linear function defined as f(x) = m*x + c, the average value over [xL, xU] is:
* (f(xL) + f(xU)) / 2, which can be rewritten as m*((xL + xU)/2) + c.
* This function calculates the slope m using the two endpoints of the line and then computes
* the y-intercept c (using c = y2 - m*x2). If the line is horizontal (zero slope), it returns the
* constant y-value. Note that precision loss may occur due to integer division and type casting.
* Also, it is assumed that fn.x1 and fn.x2 are distinct to avoid division by zero.
* @param fn The linear function defined by two points (with properties x1, y1, x2, y2).
* @param xL The lower bound of the x-interval.
* @param xU The upper bound of the x-interval.
* @return The average y-value over the interval [xL, xU].
*/
function avgY(Line memory fn, uint256 xL, uint256 xU) internal pure returns (int256) {
// If the line is horizontal, return the constant y-value.
if (fn.y1 == fn.y2) {
return fn.y2.toInt256();
}

// Calculate the slope (m = deltaY / deltaX).
int256 deltaY = fn.y2.toInt256() - fn.y1.toInt256();
int256 deltaX = fn.x2.toInt256() - fn.x1.toInt256();

// Calculate the y-intercept using one of the endpoints: c = y2 - m * x2.
int256 c = fn.y2.toInt256() - ((fn.x2.toInt256() * deltaY) / deltaX);

// Compute the average value over [xL, xU] as m*((xL + xU)/2) + c.
return ((((xL + xU).toInt256() * deltaY) / (2 * deltaX)) + c);
}
}
Loading