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
25 changes: 16 additions & 9 deletions spot-contracts/contracts/FeePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
/// is *different* from button-wood's tranche ratios.
uint256 public override targetSystemRatio;

/// @notice The range of deviation ratios which define the equilibrium zone.
/// @dev When the system's dr is within the equilibrium zone, no value is transferred
/// during a rebalance operation.
Range public equilibriumDR;

//-----------------------------------------------------------------------------
// Fee parameters

Expand Down Expand Up @@ -113,6 +118,7 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
__Ownable_init();

targetSystemRatio = 3 * ONE; // 3.0
equilibriumDR = Range({ lower: (ONE * 95) / 100, upper: (ONE * 105) / 100 });

// initializing fees
feeFnDRDown = Line({
Expand Down Expand Up @@ -153,6 +159,15 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
targetSystemRatio = targetSystemRatio_;
}

/// @notice Updates the equilibrium DR range.
/// @param equilibriumDR_ The new equilibrium DR range as tuple of a fixed point numbers with {DECIMALS} places.
function updateEquilibriumDR(Range memory equilibriumDR_) external onlyOwner {
if (equilibriumDR_.lower > equilibriumDR_.upper || equilibriumDR_.lower > ONE || equilibriumDR_.upper < ONE) {
revert InvalidRange();
}
equilibriumDR = equilibriumDR_;
}

/// @notice Updates the system fee functions.
/// @param feeFnDRDown_ The new fee function for operations that decrease DR.
/// @param feeFnDRUp_ The new fee function for operations that increase DR.
Expand Down Expand Up @@ -261,8 +276,7 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
function computeRebalanceAmount(SystemTVL memory s) external view override returns (int256 underlyingAmtIntoPerp) {
// We skip rebalancing if dr is close to 1.0
uint256 dr = computeDeviationRatio(s);
Range memory drEq = drEqZone();
if (dr >= drEq.lower && dr <= drEq.upper) {
if (dr >= equilibriumDR.lower && dr <= equilibriumDR.upper) {
return 0;
}

Expand Down Expand Up @@ -305,11 +319,4 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable {
// NOTE: We assume that perp's TVL and vault's TVL values have the same base denomination.
return s.vaultTVL.mulDiv(ONE, s.perpTVL).mulDiv(ONE, targetSystemRatio);
}

/// @return The range of deviation ratios which define the equilibrium zone.
/// @dev We infer the equilibrium from the fee function definitions, i.e) the upperDR in `feeFnDRDown`
/// and lowerDR in `feeFnDRUp`.
function drEqZone() public view returns (Range memory) {
return Range({ lower: feeFnDRDown.x2, upper: feeFnDRUp.x1 });
}
}
31 changes: 14 additions & 17 deletions spot-contracts/contracts/PerpetualTranche.sol
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,7 @@ contract PerpetualTranche is
// v2.0.0 STORAGE ADDITION

/// @notice Address of the authorized rollover vault.
/// @dev If this address is set, only the rollover vault can perform rollovers.
/// If not rollovers are publicly accessible.
/// @dev Only the authorized rollover vault can perform rollovers.
IRolloverVault public override vault;

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -569,39 +568,37 @@ contract PerpetualTranche is
}

/// @inheritdoc IPerpetualTranche
function getReserveTokensUpForRollover() external override afterStateUpdate returns (IERC20Upgradeable[] memory) {
function getReserveTokensUpForRollover()
external
override
afterStateUpdate
returns (IERC20Upgradeable[] memory activeRolloverTokens)
{
uint8 reserveCount = uint8(_reserves.length());
IERC20Upgradeable[] memory activeRolloverTokens = new IERC20Upgradeable[](reserveCount);
activeRolloverTokens = new IERC20Upgradeable[](reserveCount);

// We count the number of tokens up for rollover.
uint8 numTokensUpForRollover = 0;

// If any underlying collateral exists it can be rolled over.
IERC20Upgradeable underlying_ = _reserveAt(0);
if (underlying_.balanceOf(address(this)) > 0) {
activeRolloverTokens[0] = underlying_;
numTokensUpForRollover++;
activeRolloverTokens[numTokensUpForRollover++] = underlying_;
}

// Iterating through the reserve to find tranches that are ready to be rolled out.
for (uint8 i = 1; i < reserveCount; ++i) {
IERC20Upgradeable token = _reserveAt(i);
if (_isTimeForRollout(ITranche(address(token)))) {
activeRolloverTokens[i] = token;
numTokensUpForRollover++;
activeRolloverTokens[numTokensUpForRollover++] = token;
}
}

// We recreate a smaller array with just the tokens up for rollover.
IERC20Upgradeable[] memory rolloverTokens = new IERC20Upgradeable[](numTokensUpForRollover);
uint8 j = 0;
for (uint8 i = 0; i < reserveCount; ++i) {
if (address(activeRolloverTokens[i]) != address(0)) {
rolloverTokens[j++] = activeRolloverTokens[i];
}
// Resize array in-place
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(activeRolloverTokens, numTokensUpForRollover)
}

return rolloverTokens;
}

/// @inheritdoc IPerpetualTranche
Expand Down
10 changes: 8 additions & 2 deletions spot-contracts/contracts/RolloverVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ import { TrancheManager } from "./_utils/TrancheManager.sol";
* When ever a tranche token enters or leaves the system, we immediately invoke `_syncDeployedAsset` to update book-keeping.
* We call `_syncAsset` at the very end of every external function which changes the vault's underlying or perp balance.
*
* The perp tokens aren't considered part of the vault's asset set. However
* during the normal operations, the vault can hold transient perp tokens but they
* are immediately broken down into tranches. The vault receives perps only during rebalancing and swapping.
* At the end of those operations, the vault redeems perp tokens for the senior tranches backing them.
*
*
*/
contract RolloverVault is
ERC20BurnableUpgradeable,
Expand Down Expand Up @@ -604,7 +610,7 @@ contract RolloverVault is
// The vault continues to hold the perp dust until the subsequent `swapPerpsForUnderlying` or manual `recover(perp)`.

// We ensure that the vault's underlying token liquidity
// remains above the reserved level after swap.
// remains above the reserved level after a swap that reduces liquidity.
uint256 underlyingBalPost = underlying_.balanceOf(address(this));
if ((underlyingBalPost < underlyingBalPre) && (underlyingBalPost <= _totalReservedBalance(s.vaultTVL))) {
revert InsufficientLiquidity();
Expand Down Expand Up @@ -638,7 +644,7 @@ contract RolloverVault is
// transfer underlying out
underlying_.safeTransfer(msg.sender, underlyingAmtOut);

// Revert if swap reduces vault's available liquidity.
// We ensure that this swap strictly increases the vault's liquidity.
uint256 underlyingBalPost = underlying_.balanceOf(address(this));
if (underlyingBalPost < underlyingBalPre) {
revert InsufficientLiquidity();
Expand Down
40 changes: 38 additions & 2 deletions spot-contracts/test/FeePolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("FeePolicy", function () {
expect(f2[2]).to.eq(toPerc("1.5"));
expect(f2[3]).to.eq(toPerc("0.25"));

const drEq = await feePolicy.drEqZone();
const drEq = await feePolicy.equilibriumDR();
expect(drEq[0]).to.eq(toPerc("0.95"));
expect(drEq[1]).to.eq(toPerc("1.05"));

Expand Down Expand Up @@ -91,6 +91,42 @@ describe("FeePolicy", function () {
});
});

describe("#updateEquilibriumDR", function () {
describe("when triggered by non-owner", function () {
it("should revert", async function () {
await expect(
feePolicy.connect(otherUser).updateEquilibriumDR([toPerc("0.9"), toPerc("1.1")]),
).to.be.revertedWith("Ownable: caller is not the owner");
});
});

describe("when range is invalid", function () {
it("should revert", async function () {
await expect(feePolicy.updateEquilibriumDR([toPerc("1.2"), toPerc("1.1")])).to.be.revertedWithCustomError(
feePolicy,
"InvalidRange",
);
await expect(feePolicy.updateEquilibriumDR([toPerc("1.01"), toPerc("1.1")])).to.be.revertedWithCustomError(
feePolicy,
"InvalidRange",
);
await expect(feePolicy.updateEquilibriumDR([toPerc("0.9"), toPerc("0.99")])).to.be.revertedWithCustomError(
feePolicy,
"InvalidRange",
);
});
});

describe("when triggered by owner", function () {
it("should update the target sr", async function () {
await feePolicy.connect(deployer).updateEquilibriumDR([toPerc("0.9"), toPerc("1.1")]);
const eq = await feePolicy.equilibriumDR();
expect(eq[0]).to.eq(toPerc("0.9"));
expect(eq[1]).to.eq(toPerc("1.1"));
});
});
});

describe("#updateFees", function () {
const VALID_DOWN = toLine("0.0", "1.0", "0.99", "0.0");
const VALID_UP = toLine("1.01", "0.0", "2.0", "1.0");
Expand Down Expand Up @@ -333,7 +369,7 @@ describe("FeePolicy", function () {

describe("when deviation is within eq range", function () {
it("should compute rebalance data", async function () {
await feePolicy.updateFees(toLine("0", "0.5", "0.5", "0"), toLine("2", "0", "10", "0.5"));
await feePolicy.updateEquilibriumDR([toPerc("0.5"), toPerc("2")]);
const r1 = await feePolicy.computeRebalanceAmount({
perpTVL: toAmt("120"),
vaultTVL: toAmt("500"),
Expand Down
4 changes: 2 additions & 2 deletions spot-vaults/contracts/BillBroker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ import { InvalidARBound } from "./_interfaces/errors/BillBrokerErrors.sol";
* hold the perp tokens until the market price recovers and sell it back to the bill broker contract.
*
* Single Sided deposits:
* The pool also supports single sided deposits with either perps or usd tokens
* insofar as it brings the pool back into balance.
* The pool also supports single sided deposits with either perps or usd tokens. A
* single sided deposit is treated simply as an atomic swap and deposit.
*
*/
contract BillBroker is
Expand Down