Skip to content

Commit c612dea

Browse files
authored
Merge pull request #241 from ampleforth/fee-update
DR based flash swap fees
2 parents 6a9484e + e6c9aea commit c612dea

45 files changed

Lines changed: 4722 additions & 8948 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

spot-contracts/contracts/FeePolicy.sol

Lines changed: 240 additions & 125 deletions
Large diffs are not rendered by default.

spot-contracts/contracts/PerpetualTranche.sol

Lines changed: 60 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { BondHelpers } from "./_utils/BondHelpers.sol";
3535
* into the reserve. At any time, the reserve holds at most 2 classes of tokens
3636
* i.e) the seniors and the underlying collateral.
3737
*
38-
* Incentivized parties can "rollover" tranches approaching maturity or the underlying collateral,
38+
* The rollover vault can "rollover" tranches approaching maturity or the underlying collateral,
3939
* for newer seniors (which expire further out in the future) that belong to the updated "depositBond".
4040
*
4141
*
@@ -45,6 +45,8 @@ import { BondHelpers } from "./_utils/BondHelpers.sol";
4545
* This brings the system storage state up to date.
4646
*
4747
* CRITICAL: On the 3 main system operations: deposit, redeem and rollover;
48+
*
49+
* The system charges a fee for minting and burning perp tokens, which are paid to the vault.
4850
* We first compute fees before executing any transfers in or out of the system.
4951
* The ordering of operations is very important as the fee computation logic,
5052
* requires the system TVL as an input and which should be recorded prior to any value
@@ -57,6 +59,12 @@ import { BondHelpers } from "./_utils/BondHelpers.sol";
5759
* When computing the value of assets in the system, the code always over-values by
5860
* rounding up. When computing the value of incoming assets, the code rounds down.
5961
*
62+
* @dev Demand imbalance between perp and the vault
63+
* is restored through a "rebalancing" mechanism similar to a funding rate. When value needs to flow from perp to the vault,
64+
* the system debases the value of perp tokens by minting perp tokens to the vault.
65+
* When value needs to flow from the vault to perp, the underlying collateral tokens are
66+
* transferred from the vault into perp's reserve thereby enriching the value of perp tokens.
67+
*
6068
*/
6169
contract PerpetualTranche is
6270
ERC20BurnableUpgradeable,
@@ -381,8 +389,8 @@ contract PerpetualTranche is
381389
}
382390

383391
// Calculates the fee adjusted amount of perp tokens minted when depositing `trancheInAmt` of tranche tokens
384-
// NOTE: This operation should precede any token transfers.
385-
uint256 perpAmtMint = _computeMintAmt(trancheIn, trancheInAmt);
392+
// NOTE: This calculation should precede any token transfers.
393+
(uint256 perpAmtMint, uint256 perpFeeAmt) = _computeMintAmt(trancheIn, trancheInAmt);
386394
if (trancheInAmt <= 0 || perpAmtMint <= 0) {
387395
return 0;
388396
}
@@ -393,6 +401,9 @@ contract PerpetualTranche is
393401
// mints perp tokens to the sender
394402
_mint(msg.sender, perpAmtMint);
395403

404+
// mint fees are paid to the vault by minting perp tokens.
405+
_mint(address(vault), perpFeeAmt);
406+
396407
// post-deposit checks
397408
_enforceMintCaps(trancheIn);
398409

@@ -401,19 +412,22 @@ contract PerpetualTranche is
401412

402413
/// @inheritdoc IPerpetualTranche
403414
function redeem(
404-
uint256 perpAmtBurnt
415+
uint256 perpAmt
405416
) external override afterStateUpdate nonReentrant whenNotPaused returns (TokenAmount[] memory) {
406417
// verifies if burn amount is acceptable
407-
if (perpAmtBurnt <= 0) {
418+
if (perpAmt <= 0) {
408419
return new TokenAmount[](0);
409420
}
410421

411422
// Calculates the fee adjusted share of reserve tokens to be redeemed
412-
// NOTE: This operation should precede any token transfers.
413-
TokenAmount[] memory tokensOut = _computeRedemptionAmts(perpAmtBurnt);
423+
// NOTE: This calculation should precede any token transfers.
424+
(TokenAmount[] memory tokensOut, uint256 perpFeeAmt) = _computeRedemptionAmts(perpAmt);
414425

415426
// burns perp tokens from the sender
416-
_burn(msg.sender, perpAmtBurnt);
427+
_burn(msg.sender, perpAmt - perpFeeAmt);
428+
429+
// redemption fees are paid to transferring perp tokens
430+
transfer(address(vault), perpFeeAmt);
417431

418432
// transfers reserve tokens out
419433
uint8 tokensOutCount = uint8(tokensOut.length);
@@ -437,8 +451,8 @@ contract PerpetualTranche is
437451
revert UnacceptableRollover();
438452
}
439453

440-
// Calculates the fee adjusted amount of tranches exchanged during a rolled over
441-
// NOTE: This operation should precede any token transfers.
454+
// Calculates the amount of tranches exchanged during a rolled over
455+
// NOTE: This calculation should precede any token transfers.
442456
RolloverData memory r = _computeRolloverAmt(trancheIn, tokenOut, trancheInAmtAvailable);
443457

444458
// Verifies if rollover amount is acceptable
@@ -455,6 +469,15 @@ contract PerpetualTranche is
455469
return r;
456470
}
457471

472+
/// @inheritdoc IPerpetualTranche
473+
/// @dev CAUTION: Only the whitelisted vault can call this function.
474+
/// The logic controlling the frequency and magnitude of debasement should be vetted.
475+
function rebalanceToVault(
476+
uint256 underlyingAmtToTransfer
477+
) external override onlyVault afterStateUpdate nonReentrant whenNotPaused {
478+
_mint(address(vault), underlyingAmtToTransfer.mulDiv(totalSupply(), _reserveValue()));
479+
}
480+
458481
/// @inheritdoc IPerpetualTranche
459482
function getDepositBond() external override afterStateUpdate returns (IBondController) {
460483
return _depositBond;
@@ -564,14 +587,14 @@ contract PerpetualTranche is
564587
if (!_isDepositTranche(trancheIn)) {
565588
revert UnexpectedAsset();
566589
}
567-
return _computeMintAmt(trancheIn, trancheInAmt);
590+
(uint256 perpAmtMint, ) = _computeMintAmt(trancheIn, trancheInAmt);
591+
return perpAmtMint;
568592
}
569593

570594
/// @inheritdoc IPerpetualTranche
571-
function computeRedemptionAmts(
572-
uint256 perpAmtBurnt
573-
) external override afterStateUpdate returns (TokenAmount[] memory) {
574-
return _computeRedemptionAmts(perpAmtBurnt);
595+
function computeRedemptionAmts(uint256 perpAmt) external override afterStateUpdate returns (TokenAmount[] memory) {
596+
(TokenAmount[] memory tokensOut, ) = _computeRedemptionAmts(perpAmt);
597+
return tokensOut;
575598
}
576599

577600
/// @inheritdoc IPerpetualTranche
@@ -724,7 +747,10 @@ contract PerpetualTranche is
724747
}
725748

726749
/// @dev Computes the fee adjusted perp mint amount for given amount of tranche tokens deposited into the reserve.
727-
function _computeMintAmt(ITranche trancheIn, uint256 trancheInAmt) private view returns (uint256) {
750+
function _computeMintAmt(
751+
ITranche trancheIn,
752+
uint256 trancheInAmt
753+
) private view returns (uint256 perpAmtMint, uint256 perpFeeAmt) {
728754
uint256 valueIn = _computeReserveTrancheValue(
729755
trancheIn,
730756
_depositBond,
@@ -740,68 +766,56 @@ contract PerpetualTranche is
740766

741767
// Compute mint amt
742768
uint256 perpSupply = totalSupply();
743-
uint256 perpAmtMint = valueIn;
769+
perpAmtMint = valueIn;
744770
if (perpSupply > 0) {
745771
perpAmtMint = perpAmtMint.mulDiv(perpSupply, _reserveValue());
746772
}
747773

748-
// The mint fees are settled by simply minting fewer perps.
774+
// Compute the fee amount
749775
if (feePerc > 0) {
750-
perpAmtMint = perpAmtMint.mulDiv(ONE - feePerc, ONE);
776+
perpFeeAmt = perpAmtMint.mulDiv(feePerc, ONE, MathUpgradeable.Rounding.Up);
777+
perpAmtMint -= perpFeeAmt;
751778
}
752-
753-
return perpAmtMint;
754779
}
755780

756781
/// @dev Computes the reserve token amounts redeemed when a given number of perps are burnt.
757-
function _computeRedemptionAmts(uint256 perpAmtBurnt) private view returns (TokenAmount[] memory) {
782+
function _computeRedemptionAmts(
783+
uint256 perpAmt
784+
) private view returns (TokenAmount[] memory reserveTokens, uint256 perpFeeAmt) {
758785
uint256 perpSupply = totalSupply();
759786

760787
//-----------------------------------------------------------------------------
761788
// We charge no burn fee when interacting with other parts of the system.
762789
uint256 feePerc = _isProtocolCaller() ? 0 : feePolicy.computePerpBurnFeePerc();
763790
//-----------------------------------------------------------------------------
764791

792+
// Compute the fee amount
793+
if (feePerc > 0) {
794+
perpFeeAmt = perpAmt.mulDiv(feePerc, ONE, MathUpgradeable.Rounding.Up);
795+
perpAmt -= perpFeeAmt;
796+
}
797+
765798
// Compute redemption amounts
766799
uint8 reserveCount = uint8(_reserves.length());
767-
TokenAmount[] memory reserveTokens = new TokenAmount[](reserveCount);
800+
reserveTokens = new TokenAmount[](reserveCount);
768801
for (uint8 i = 0; i < reserveCount; ++i) {
769802
IERC20Upgradeable tokenOut = _reserveAt(i);
770803
reserveTokens[i] = TokenAmount({
771804
token: tokenOut,
772-
amount: tokenOut.balanceOf(address(this)).mulDiv(perpAmtBurnt, perpSupply)
805+
amount: tokenOut.balanceOf(address(this)).mulDiv(perpAmt, perpSupply)
773806
});
774-
775-
// The burn fees are settled by simply redeeming for fewer tranches.
776-
if (feePerc > 0) {
777-
reserveTokens[i].amount = reserveTokens[i].amount.mulDiv(ONE - feePerc, ONE);
778-
}
779807
}
780808

781-
return (reserveTokens);
809+
return (reserveTokens, perpFeeAmt);
782810
}
783811

784812
/// @dev Computes the amount of reserve tokens that can be rolled out for the given amount of tranches deposited.
785-
/// The relative ratios of tokens In/Out are adjusted based on the current rollover fee perc.
786813
function _computeRolloverAmt(
787814
ITranche trancheIn,
788815
IERC20Upgradeable tokenOut,
789816
uint256 trancheInAmtAvailable
790817
) private view returns (RolloverData memory) {
791818
//-----------------------------------------------------------------------------
792-
// The rollover fees are settled by, adjusting the exchange rate
793-
// between `trancheInAmt` and `tokenOutAmt`.
794-
//
795-
int256 feePerc = feePolicy.computePerpRolloverFeePerc(
796-
feePolicy.computeDeviationRatio(
797-
SubscriptionParams({
798-
perpTVL: _reserveValue(),
799-
vaultTVL: vault.getTVL(),
800-
seniorTR: _depositBond.getSeniorTrancheRatio()
801-
})
802-
)
803-
);
804-
//-----------------------------------------------------------------------------
805819

806820
// We compute "price" as the value of a unit token.
807821
// The perp, tranche tokens and the underlying are denominated as fixed point numbers
@@ -832,8 +846,8 @@ contract PerpetualTranche is
832846
return RolloverData({ trancheInAmt: 0, tokenOutAmt: 0 });
833847
}
834848
//-----------------------------------------------------------------------------
835-
// Basic rollover with fees:
836-
// (1 +/- f) . (trancheInAmt . trancheInPrice) = (tokenOutAmt . tokenOutPrice)
849+
// Basic rollover:
850+
// (trancheInAmt . trancheInPrice) = (tokenOutAmt . tokenOutPrice)
837851
//-----------------------------------------------------------------------------
838852

839853
// Using perp's tokenOutBalance, we calculate the amount of tokens in to rollover
@@ -844,17 +858,6 @@ contract PerpetualTranche is
844858
trancheInAmt: tokenOutBalance.mulDiv(tokenOutPrice, trancheInPrice, MathUpgradeable.Rounding.Up)
845859
});
846860

847-
// A positive fee percentage implies that perp charges rotators by
848-
// offering tranchesOut for a premium, i.e) more tranches in.
849-
if (feePerc > 0) {
850-
r.trancheInAmt = r.trancheInAmt.mulDiv(ONE, ONE - feePerc.toUint256(), MathUpgradeable.Rounding.Up);
851-
}
852-
// A negative fee percentage (or a reward) implies that perp pays the rotators by
853-
// offering tranchesOut at a discount, i.e) fewer tranches in.
854-
else if (feePerc < 0) {
855-
r.trancheInAmt = r.trancheInAmt.mulDiv(ONE, ONE + feePerc.abs(), MathUpgradeable.Rounding.Up);
856-
}
857-
858861
//-----------------------------------------------------------------------------
859862

860863
// When the trancheInAmt exceeds trancheInAmtAvailable:
@@ -864,19 +867,6 @@ contract PerpetualTranche is
864867
// Given the amount of tranches In, we compute the amount of tokens out
865868
r.trancheInAmt = trancheInAmtAvailable;
866869
r.tokenOutAmt = trancheInAmtAvailable.mulDiv(trancheInPrice, tokenOutPrice);
867-
868-
// A positive fee percentage implies that perp charges rotators by
869-
// accepting tranchesIn at a discount, i.e) fewer tokens out.
870-
// This results in perp enrichment.
871-
if (feePerc > 0) {
872-
r.tokenOutAmt = r.tokenOutAmt.mulDiv(ONE - feePerc.abs(), ONE);
873-
}
874-
// A negative fee percentage (or a reward) implies that perp pays the rotators by
875-
// accepting tranchesIn at a premium, i.e) more tokens out.
876-
// This results in perp debasement.
877-
else if (feePerc < 0) {
878-
r.tokenOutAmt = r.tokenOutAmt.mulDiv(ONE + feePerc.abs(), ONE);
879-
}
880870
}
881871

882872
return r;

0 commit comments

Comments
 (0)