diff --git a/spot-contracts/contracts/FeePolicy.sol b/spot-contracts/contracts/FeePolicy.sol index ce92961b..98a88494 100644 --- a/spot-contracts/contracts/FeePolicy.sol +++ b/spot-contracts/contracts/FeePolicy.sol @@ -427,4 +427,29 @@ contract FeePolicy is IFeePolicy, OwnableUpgradeable { // Deduct protocol fee from value transfer r.underlyingAmtIntoPerp -= (perpDebasement ? int256(-1) : int256(1)) * r.protocolFeeUnderlyingAmt.toInt256(); } + + /// @inheritdoc IFeePolicy + function computeDREquilibriumSplit( + uint256 underlyingAmt, + uint256 seniorTR + ) external view override returns (uint256 perpUnderlyingAmt, uint256 vaultUnderlyingAmt) { + uint256 juniorTR = (TRANCHE_RATIO_GRANULARITY - seniorTR); + perpUnderlyingAmt = underlyingAmt.mulDiv(seniorTR, seniorTR + juniorTR.mulDiv(targetSubscriptionRatio, ONE)); + vaultUnderlyingAmt = underlyingAmt - perpUnderlyingAmt; + } + + /// @inheritdoc IFeePolicy + function computeDRNeutralSplit( + uint256 perpAmtAvailable, + uint256 vaultNoteAmtAvailable, + uint256 perpSupply, + uint256 vaultNoteSupply + ) external pure override returns (uint256 perpAmt, uint256 vaultNoteAmt) { + perpAmt = perpAmtAvailable; + vaultNoteAmt = vaultNoteSupply.mulDiv(perpAmt, perpSupply); + if (vaultNoteAmt > vaultNoteAmtAvailable) { + vaultNoteAmt = vaultNoteAmtAvailable; + perpAmt = perpAmtAvailable.mulDiv(vaultNoteAmt, vaultNoteSupply); + } + } } diff --git a/spot-contracts/contracts/RolloverVault.sol b/spot-contracts/contracts/RolloverVault.sol index 05ce50e6..e6c7e5b3 100644 --- a/spot-contracts/contracts/RolloverVault.sol +++ b/spot-contracts/contracts/RolloverVault.sol @@ -21,6 +21,8 @@ import { BondTranches, BondTranchesHelpers } from "./_utils/BondTranchesHelpers. import { TrancheHelpers } from "./_utils/TrancheHelpers.sol"; import { BondHelpers } from "./_utils/BondHelpers.sol"; import { PerpHelpers } from "./_utils/PerpHelpers.sol"; +import { ERC20Helpers } from "./_utils/ERC20Helpers.sol"; +import { TrancheManager } from "./_utils/TrancheManager.sol"; /* * @title RolloverVault @@ -71,12 +73,16 @@ contract RolloverVault is // ERC20 operations using SafeERC20Upgradeable for IERC20Upgradeable; using SafeERC20Upgradeable for ITranche; + using ERC20Helpers for IERC20Upgradeable; // math using MathUpgradeable for uint256; using SignedMathUpgradeable for int256; using SafeCastUpgradeable for int256; + /// Allow linking TrancheManager + /// @custom:oz-upgrades-unsafe-allow external-library-linking + //------------------------------------------------------------------------- // Events @@ -388,7 +394,7 @@ contract RolloverVault is // if bond has matured, redeem the tranche token if (bond.secondsToMaturity() <= 0) { // execute redemption - _execMatureTrancheRedemption(bond, tranche, trancheBalance); + TrancheManager.execMatureTrancheRedemption(bond, tranche, trancheBalance); } // if not redeem using proportional balances // redeems this tranche and it's siblings if the vault holds balances. @@ -397,7 +403,7 @@ contract RolloverVault is // We also skip if the tranche balance is too low as immature redemption will be a no-op. else if (tranche == bt.tranches[0] && trancheBalance > TRANCHE_DUST_AMT) { // execute redemption - _execImmatureTrancheRedemption(bond, bt); + TrancheManager.execImmatureTrancheRedemption(bond, bt); } } @@ -432,38 +438,127 @@ contract RolloverVault is revert UnexpectedAsset(); } + /// @inheritdoc IRolloverVault + /// @dev This operation pushes the system back into balance, we thus charge no fees. + function mint2( + uint256 underlyingAmtIn + ) external override nonReentrant whenNotPaused returns (uint256 perpAmt, uint256 vaultNoteAmt) { + // Compute perp vault split + IPerpetualTranche perp_ = perp; + IERC20Upgradeable underlying_ = underlying; + SubscriptionParams memory s = _querySubscriptionState(perp_); + (uint256 underlyingAmtIntoPerp, uint256 underlyingAmtIntoVault) = feePolicy.computeDREquilibriumSplit( + underlyingAmtIn, + s.seniorTR + ); + + // Compute perp amount and vault note amount to mint + perpAmt = underlyingAmtIntoPerp.mulDiv(perp_.totalSupply(), s.perpTVL); + vaultNoteAmt = computeMintAmt(underlyingAmtIntoVault, 0); + + // Transfer underlying tokens from user + underlying_.safeTransferFrom(msg.sender, address(this), underlyingAmtIn); + + // Mint perps to user + _trancheAndMintPerps(perp_, underlying_, s.perpTVL, s.seniorTR, perpAmt); + IERC20Upgradeable(address(perp_)).safeTransfer(msg.sender, perpAmt); + + // Mint vault notes to user + _mint(msg.sender, vaultNoteAmt); + + // Sync underlying + _syncAsset(underlying_); + } + + /// @inheritdoc IRolloverVault + /// @dev This operation maintains the system's balance, we thus charge no fees. + function redeem2( + uint256 perpAmtAvailable, + uint256 vaultNoteAmtAvailable + ) + external + override + nonReentrant + whenNotPaused + returns (uint256 perpAmt, uint256 vaultNoteAmt, TokenAmount[] memory returnedTokens) + { + // Compute perp vault split + IPerpetualTranche perp_ = perp; + (perpAmt, vaultNoteAmt) = feePolicy.computeDRNeutralSplit( + perpAmtAvailable, + vaultNoteAmtAvailable, + perp_.totalSupply(), + totalSupply() + ); + + // Redeem vault notes + TokenAmount[] memory vaultTokens = computeRedemptionAmts(vaultNoteAmt, 0); + _burn(msg.sender, vaultNoteAmt); + + // Transfer perps from user and redeem + IERC20Upgradeable(address(perp_)).safeTransferFrom(msg.sender, address(this), perpAmt); + TokenAmount[] memory perpTokens = perp.redeem(perpAmt); + + // Compute final list of tokens to return to the user + // assert(underlying == perpTokens[0].token && underlying == vaultTokens[0].token); + returnedTokens = new TokenAmount[](perpTokens.length + vaultTokens.length - 1); + returnedTokens[0] = TokenAmount({ + token: perpTokens[0].token, + amount: (perpTokens[0].amount + vaultTokens[0].amount) + }); + returnedTokens[0].token.safeTransfer(msg.sender, returnedTokens[0].amount); + + // perp tokens + for (uint8 i = 1; i < uint8(perpTokens.length); i++) { + returnedTokens[i] = perpTokens[i]; + returnedTokens[i].token.safeTransfer(msg.sender, returnedTokens[i].amount); + } + + // vault tokens + for (uint8 i = 1; i < uint8(vaultTokens.length); i++) { + returnedTokens[i - 1 + perpTokens.length] = vaultTokens[i]; + vaultTokens[i].token.safeTransfer(msg.sender, vaultTokens[i].amount); + + // sync balances + _syncDeployedAsset(vaultTokens[i].token); + } + + // sync underlying + _syncAsset(perpTokens[0].token); + } + /// @inheritdoc IVault function deposit(uint256 underlyingAmtIn) external override nonReentrant whenNotPaused returns (uint256) { - // Calculates the fee adjusted amount of notes minted when depositing `underlyingAmtIn` of underlying tokens. + // Calculates the fee adjusted amount of vault notes minted when depositing `underlyingAmtIn` of underlying tokens. // NOTE: This operation should precede any token transfers. - uint256 notes = computeMintAmt(underlyingAmtIn); - if (underlyingAmtIn <= 0 || notes <= 0) { + uint256 vaultNoteAmt = computeMintAmt(underlyingAmtIn, feePolicy.computeVaultMintFeePerc()); + if (underlyingAmtIn <= 0 || vaultNoteAmt <= 0) { return 0; } // transfer user assets in underlying.safeTransferFrom(msg.sender, address(this), underlyingAmtIn); - // mint notes - _mint(msg.sender, notes); + // mint vault notes + _mint(msg.sender, vaultNoteAmt); // sync underlying _syncAsset(underlying); - return notes; + return vaultNoteAmt; } /// @inheritdoc IVault - function redeem(uint256 notes) public override nonReentrant whenNotPaused returns (TokenAmount[] memory) { - if (notes <= 0) { + function redeem(uint256 vaultNoteAmt) public override nonReentrant whenNotPaused returns (TokenAmount[] memory) { + if (vaultNoteAmt <= 0) { return new TokenAmount[](0); } // Calculates the fee adjusted share of vault tokens to be redeemed // NOTE: This operation should precede any token transfers. - TokenAmount[] memory redemptions = computeRedemptionAmts(notes); + TokenAmount[] memory redemptions = computeRedemptionAmts(vaultNoteAmt, feePolicy.computeVaultBurnFeePerc()); - // burn notes - _burn(msg.sender, notes); + // burn vault notes + _burn(msg.sender, vaultNoteAmt); // transfer assets out uint8 redemptionsCount = uint8(redemptions.length); @@ -486,9 +581,9 @@ contract RolloverVault is } /// @inheritdoc IVault - function recoverAndRedeem(uint256 notes) external override returns (TokenAmount[] memory) { + function recoverAndRedeem(uint256 vaultNoteAmt) external override returns (TokenAmount[] memory) { recover(); - return redeem(notes); + return redeem(vaultNoteAmt); } /// @inheritdoc IRolloverVault @@ -635,61 +730,64 @@ contract RolloverVault is //-------------------------------------------------------------------------- // External & Public read methods - /// @inheritdoc IVault - function computeMintAmt(uint256 underlyingAmtIn) public view returns (uint256) { - //----------------------------------------------------------------------------- - uint256 feePerc = feePolicy.computeVaultMintFeePerc(); - //----------------------------------------------------------------------------- + /// @notice Computes the amount of vault notes minted when given amount of underlying asset tokens + /// are deposited into the system. + /// @param underlyingAmtIn The amount underlying tokens to be deposited into the vault. + /// @param feePerc The percentage of minted vault notes paid as fees. + /// @return vaultNoteAmtMint The amount of vault notes to be minted. + function computeMintAmt(uint256 underlyingAmtIn, uint256 feePerc) public view returns (uint256 vaultNoteAmtMint) { + uint256 noteSupply = totalSupply(); + //----------------------------------------------------------------------------- // Compute mint amt - uint256 noteSupply = totalSupply(); - uint256 notes = (noteSupply > 0) + vaultNoteAmtMint = (noteSupply > 0) ? noteSupply.mulDiv(underlyingAmtIn, getTVL()) : (underlyingAmtIn * INITIAL_RATE); // The mint fees are settled by simply minting fewer vault notes. - notes = notes.mulDiv(ONE - feePerc, ONE); - return notes; + vaultNoteAmtMint = vaultNoteAmtMint.mulDiv(ONE - feePerc, ONE); } - /// @inheritdoc IVault - function computeRedemptionAmts(uint256 noteAmtBurnt) public view returns (TokenAmount[] memory) { + /// @notice Computes the amount of asset tokens returned for redeeming vault notes. + /// @param vaultNoteAmtRedeemed The amount of vault notes to be redeemed. + /// @param feePerc The percentage of redeemed vault notes paid as fees. + /// @return returnedTokens The list of asset tokens and amounts returned. + function computeRedemptionAmts( + uint256 vaultNoteAmtRedeemed, + uint256 feePerc + ) public view returns (TokenAmount[] memory returnedTokens) { uint256 noteSupply = totalSupply(); - //----------------------------------------------------------------------------- - uint256 feePerc = feePolicy.computeVaultBurnFeePerc(); //----------------------------------------------------------------------------- uint8 assetCount_ = 1 + uint8(_deployed.length()); // aggregating vault assets to be redeemed - TokenAmount[] memory redemptions = new TokenAmount[](assetCount_); + returnedTokens = new TokenAmount[](assetCount_); // underlying share to be redeemed IERC20Upgradeable underlying_ = underlying; - redemptions[0] = TokenAmount({ + returnedTokens[0] = TokenAmount({ token: underlying_, - amount: underlying_.balanceOf(address(this)).mulDiv(noteAmtBurnt, noteSupply) + amount: underlying_.balanceOf(address(this)).mulDiv(vaultNoteAmtRedeemed, noteSupply) }); - redemptions[0].amount = redemptions[0].amount.mulDiv(ONE - feePerc, ONE); + returnedTokens[0].amount = returnedTokens[0].amount.mulDiv(ONE - feePerc, ONE); for (uint8 i = 1; i < assetCount_; ++i) { // tranche token share to be redeemed IERC20Upgradeable token = IERC20Upgradeable(_deployed.at(i - 1)); - redemptions[i] = TokenAmount({ + returnedTokens[i] = TokenAmount({ token: token, - amount: token.balanceOf(address(this)).mulDiv(noteAmtBurnt, noteSupply) + amount: token.balanceOf(address(this)).mulDiv(vaultNoteAmtRedeemed, noteSupply) }); // deduct redemption fee - redemptions[i].amount = redemptions[i].amount.mulDiv(ONE - feePerc, ONE); + returnedTokens[i].amount = returnedTokens[i].amount.mulDiv(ONE - feePerc, ONE); // in case the redemption amount is just dust, we skip - if (redemptions[i].amount < TRANCHE_DUST_AMT) { - redemptions[i].amount = 0; + if (returnedTokens[i].amount < TRANCHE_DUST_AMT) { + returnedTokens[i].amount = 0; } } - - return redemptions; } /// @inheritdoc IVault @@ -704,7 +802,7 @@ contract RolloverVault is ITranche tranche = ITranche(_deployed.at(i)); uint256 balance = tranche.balanceOf(address(this)); if (balance > TRANCHE_DUST_AMT) { - totalValue += _computeVaultTrancheValue(tranche, underlying, balance); + totalValue += TrancheManager.computeTrancheValue(address(tranche), address(underlying), balance); } } @@ -723,7 +821,10 @@ contract RolloverVault is // Deployed asset else if (_deployed.contains(address(token))) { ITranche tranche = ITranche(address(token)); - return (balance > TRANCHE_DUST_AMT) ? _computeVaultTrancheValue(tranche, underlying, balance) : 0; + return + (balance > TRANCHE_DUST_AMT) + ? TrancheManager.computeTrancheValue(address(tranche), address(underlying), balance) + : 0; } // Not a vault asset, so returning zero @@ -814,7 +915,7 @@ contract RolloverVault is // if bond has matured, redeem the tranche token if (bond.secondsToMaturity() <= 0) { // execute redemption - _execMatureTrancheRedemption(bond, tranche, trancheBalance); + TrancheManager.execMatureTrancheRedemption(bond, tranche, trancheBalance); // sync deployed asset _syncDeployedAsset(tranche); @@ -825,7 +926,7 @@ contract RolloverVault is else if (trancheBalance > TRANCHE_DUST_AMT) { // execute redemption BondTranches memory bt = bond.getTranches(); - _execImmatureTrancheRedemption(bond, bt); + TrancheManager.execImmatureTrancheRedemption(bond, bt); // sync deployed asset, i.e) current tranche and its sibling. _syncDeployedAsset(bt.tranches[0]); @@ -883,29 +984,13 @@ contract RolloverVault is _tranche(depositBond, underlying_, underylingAmtToTranche); // Mint perps - _checkAndApproveMax(trancheIntoPerp, address(perp_), seniorAmtToDeposit); + IERC20Upgradeable(trancheIntoPerp).checkAndApproveMax(address(perp_), seniorAmtToDeposit); perp_.deposit(trancheIntoPerp, seniorAmtToDeposit); // sync holdings _syncDeployedAsset(trancheIntoPerp); } - /// @dev Given a bond and its tranche data, deposits the provided amount into the bond - /// and receives tranche tokens in return. - /// Performs some book-keeping to keep track of the vault's assets. - function _tranche(IBondController bond, IERC20Upgradeable underlying_, uint256 underlyingAmt) private { - // Get bond tranches - BondTranches memory bt = bond.getTranches(); - - // amount is tranched - _checkAndApproveMax(underlying_, address(bond), underlyingAmt); - bond.deposit(underlyingAmt); - - // sync holdings - _syncDeployedAsset(bt.tranches[0]); - _syncDeployedAsset(bt.tranches[1]); - } - /// @dev Rolls over freshly tranched tokens from the given bond for older tranches (close to maturity) from perp. /// Redeems intermediate tranches for underlying if possible. /// Performs some book-keeping to keep track of the vault's assets. @@ -925,7 +1010,7 @@ contract RolloverVault is uint256 trancheInAmtAvailable = trancheIntoPerp.balanceOf(address(this)); // Approve once for all rollovers - _checkAndApproveMax(trancheIntoPerp, address(perp_), trancheInAmtAvailable); + IERC20Upgradeable(trancheIntoPerp).checkAndApproveMax(address(perp_), trancheInAmtAvailable); // We pair the senior tranche token held by the vault (from the deposit bond) // with each of the perp's tokens available for rollovers and execute a rollover. @@ -964,31 +1049,16 @@ contract RolloverVault is return (rollover); } - /// @dev Low level method that redeems the given mature tranche for the underlying asset. - /// It interacts with the button-wood bond contract. - /// This function should NOT be called directly, use `recover()` or `_redeemTranche(tranche)` - /// which wrap this function with the internal book-keeping necessary, - /// to keep track of the vault's assets. - function _execMatureTrancheRedemption(IBondController bond, ITranche tranche, uint256 amount) private { - if (!bond.isMature()) { - bond.mature(); - } - bond.redeemMature(address(tranche), amount); - } + /// @dev Given a bond, deposits the provided amount into the bond + /// and receives tranche tokens in return. + /// Performs some book-keeping to keep track of the vault's assets. + function _tranche(IBondController bond, IERC20Upgradeable underlying_, uint256 underlyingAmt) private { + // Tranche + ITranche[2] memory t = bond.approveAndDeposit(underlying_, underlyingAmt); - /// @dev Low level method that redeems the given tranche for the underlying asset, before maturity. - /// If the vault holds sibling tranches with proportional balances, those will also get redeemed. - /// It interacts with the button-wood bond contract. - /// This function should NOT be called directly, use `recover()` or `recover(tranche)` - /// which wrap this function with the internal book-keeping necessary, - /// to keep track of the vault's assets. - function _execImmatureTrancheRedemption(IBondController bond, BondTranches memory bt) private { - uint256[] memory trancheAmts = bt.computeRedeemableTrancheAmounts(address(this)); - - // NOTE: It is guaranteed that if one tranche amount is zero, all amounts are zeros. - if (trancheAmts[0] > 0) { - bond.redeem(trancheAmts); - } + // sync holdings + _syncDeployedAsset(t[0]); + _syncDeployedAsset(t[1]); } /// @dev Syncs balance and updates the deployed list based on the vault's token balance. @@ -1014,14 +1084,6 @@ contract RolloverVault is emit AssetSynced(token, token.balanceOf(address(this))); } - /// @dev Checks if the spender has sufficient allowance. If not, approves the maximum possible amount. - function _checkAndApproveMax(IERC20Upgradeable token, address spender, uint256 amount) private { - uint256 allowance = token.allowance(address(this), spender); - if (allowance < amount) { - token.safeApprove(spender, type(uint256).max); - } - } - /// @dev Queries the current subscription state of the perp and vault systems. function _querySubscriptionState(IPerpetualTranche perp_) private returns (SubscriptionParams memory) { return @@ -1033,21 +1095,10 @@ contract RolloverVault is } //-------------------------------------------------------------------------- - // Private methods - - /// @dev Computes the value of the given amount of tranche tokens, based on it's current CDR. - /// Value is denominated in the underlying collateral. - function _computeVaultTrancheValue( - ITranche tranche, - IERC20Upgradeable collateralToken, - uint256 trancheAmt - ) private view returns (uint256) { - (uint256 trancheClaim, uint256 trancheSupply) = tranche.getTrancheCollateralization(collateralToken); - return trancheClaim.mulDiv(trancheAmt, trancheSupply, MathUpgradeable.Rounding.Up); - } + // Private view methods /// @dev Computes the balance of underlying tokens to NOT be used for any operation. - function _totalReservedBalance(uint256 perpTVL, uint256 seniorTR) private view returns (uint256) { + function _totalReservedBalance(uint256 perpTVL, uint256 seniorTR) internal view returns (uint256) { return MathUpgradeable.max( reservedUnderlyingBal, diff --git a/spot-contracts/contracts/RouterV2.sol b/spot-contracts/contracts/RouterV2.sol index a81ea85e..23b305d8 100644 --- a/spot-contracts/contracts/RouterV2.sol +++ b/spot-contracts/contracts/RouterV2.sol @@ -11,6 +11,7 @@ import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import { BondTranches, BondTranchesHelpers } from "./_utils/BondTranchesHelpers.sol"; import { BondHelpers } from "./_utils/BondHelpers.sol"; +import { ERC20Helpers } from "./_utils/ERC20Helpers.sol"; /** * @title RouterV2 @@ -30,6 +31,7 @@ contract RouterV2 { using SafeERC20Upgradeable for IERC20Upgradeable; using SafeERC20Upgradeable for ITranche; using SafeERC20Upgradeable for IPerpetualTranche; + using ERC20Helpers for IERC20Upgradeable; /// @notice Calculates the amount of tranche tokens minted after depositing into the deposit bond. /// @dev Used by off-chain services to preview a tranche operation. @@ -64,14 +66,14 @@ contract RouterV2 { collateralToken.safeTransferFrom(msg.sender, address(this), collateralAmount); // approves collateral to be tranched - _checkAndApproveMax(collateralToken, address(bond), collateralAmount); + collateralToken.checkAndApproveMax(address(bond), collateralAmount); // tranches collateral bond.deposit(collateralAmount); // uses senior tranches to mint perps uint256 trancheAmt = bt.tranches[0].balanceOf(address(this)); - _checkAndApproveMax(bt.tranches[0], address(perp), trancheAmt); + IERC20Upgradeable(bt.tranches[0]).checkAndApproveMax(address(perp), trancheAmt); perp.deposit(bt.tranches[0], trancheAmt); // transfers remaining junior tranches back @@ -86,12 +88,4 @@ contract RouterV2 { // transfers perp tokens back perp.safeTransfer(msg.sender, perp.balanceOf(address(this))); } - - /// @dev Checks if the spender has sufficient allowance. If not, approves the maximum possible amount. - function _checkAndApproveMax(IERC20Upgradeable token, address spender, uint256 amount) private { - uint256 allowance = token.allowance(address(this), spender); - if (allowance < amount) { - token.safeApprove(spender, type(uint256).max); - } - } } diff --git a/spot-contracts/contracts/_interfaces/IFeePolicy.sol b/spot-contracts/contracts/_interfaces/IFeePolicy.sol index 8df69da1..a4b96968 100644 --- a/spot-contracts/contracts/_interfaces/IFeePolicy.sol +++ b/spot-contracts/contracts/_interfaces/IFeePolicy.sol @@ -45,4 +45,30 @@ interface IFeePolicy { /// @return r Rebalance data, magnitude and direction of value flow between perp and the rollover vault /// expressed in the underlying token amount and the protocol's cut. function computeRebalanceData(SubscriptionParams memory s) external view returns (RebalanceData memory r); + + /// @notice Computes the dr-equilibrium split of underlying tokens into perp and the vault. + /// @dev The this basically the `targetSr` adjusted bond ratio. + /// @param underlyingAmt The amount of underlying tokens to split. + /// @param seniorTR The tranche ratio of seniors accepted by perp. + /// @return underlyingAmtIntoPerp The amount of underlying tokens to go into perp. + /// @return underlyingAmtIntoVault The amount of underlying tokens to go into the vault. + function computeDREquilibriumSplit( + uint256 underlyingAmt, + uint256 seniorTR + ) external view returns (uint256 underlyingAmtIntoPerp, uint256 underlyingAmtIntoVault); + + /// @notice Computes the dr-neutral split of perp tokens and vault notes. + /// @dev The "system ratio" or the ratio of assets in the system as it stands. + /// @param perpAmtAvailable The available amount of perp tokens. + /// @param vaultNoteAmtAvailable The available amount of vault notes. + /// @param perpSupply The total supply of perp tokens. + /// @param vaultNoteSupply The total supply of vault notes. + /// @return perpAmt The amount of perp tokens, with the same share of total supply as the vault notes. + /// @return vaultNoteAmt The amount of vault notes, with the same share of total supply as the perp tokens. + function computeDRNeutralSplit( + uint256 perpAmtAvailable, + uint256 vaultNoteAmtAvailable, + uint256 perpSupply, + uint256 vaultNoteSupply + ) external view returns (uint256, uint256); } diff --git a/spot-contracts/contracts/_interfaces/IRolloverVault.sol b/spot-contracts/contracts/_interfaces/IRolloverVault.sol index 60a5dab6..7ccc0847 100644 --- a/spot-contracts/contracts/_interfaces/IRolloverVault.sol +++ b/spot-contracts/contracts/_interfaces/IRolloverVault.sol @@ -2,13 +2,30 @@ pragma solidity ^0.8.0; import { IVault } from "./IVault.sol"; -import { SubscriptionParams } from "./CommonTypes.sol"; +import { SubscriptionParams, TokenAmount } from "./CommonTypes.sol"; interface IRolloverVault is IVault { /// @notice Gradually transfers value between the perp and vault, to bring the system back into balance. /// @dev The rebalance function can be executed at-most once a day. function rebalance() external; + /// @notice Batch operation to mint both perp and rollover vault tokens. + /// @param underlyingAmtIn The amount of underlying tokens to be tranched. + /// @return perpAmt The amount of perp tokens minted. + /// @return vaultNoteAmt The amount of vault notes minted. + function mint2(uint256 underlyingAmtIn) external returns (uint256 perpAmt, uint256 vaultNoteAmt); + + /// @notice Batch operation to redeem both perp and rollover vault tokens for the underlying collateral and tranches. + /// @param perpAmtAvailable The amount of perp tokens available to redeem. + /// @param vaultNoteAmtAvailable The amount of vault notes available to redeem. + /// @return perpAmtBurnt The amount of perp tokens redeemed. + /// @return vaultNoteAmtBurnt The amount of vault notes redeemed. + /// @return returnedTokens The list of asset tokens and amounts returned. + function redeem2( + uint256 perpAmtAvailable, + uint256 vaultNoteAmtAvailable + ) external returns (uint256 perpAmtBurnt, uint256 vaultNoteAmtBurnt, TokenAmount[] memory returnedTokens); + /// @notice Allows users to swap their underlying tokens for perps held by the vault. /// @param underlyingAmtIn The amount of underlying tokens swapped in. /// @return The amount of perp tokens swapped out. diff --git a/spot-contracts/contracts/_interfaces/IVault.sol b/spot-contracts/contracts/_interfaces/IVault.sol index 28cd8258..090734c3 100644 --- a/spot-contracts/contracts/_interfaces/IVault.sol +++ b/spot-contracts/contracts/_interfaces/IVault.sol @@ -82,15 +82,4 @@ interface IVault is IERC20Upgradeable { /// @param token The address of a token to check. /// @return If the given token is held by the vault. function isVaultAsset(IERC20Upgradeable token) external view returns (bool); - - /// @notice Computes the amount of notes minted when given amount of underlying asset tokens - /// are deposited into the system. - /// @param amount The amount tokens to be deposited into the vault. - /// @return The amount of notes to be minted. - function computeMintAmt(uint256 amount) external returns (uint256); - - /// @notice Computes the amount of asset tokens redeemed when burning given number of vault notes. - /// @param notes The amount of notes to be burnt. - /// @return The list of asset tokens and amounts redeemed. - function computeRedemptionAmts(uint256 notes) external returns (TokenAmount[] memory); } diff --git a/spot-contracts/contracts/_utils/BondHelpers.sol b/spot-contracts/contracts/_utils/BondHelpers.sol index b9aefc14..3903fef9 100644 --- a/spot-contracts/contracts/_utils/BondHelpers.sol +++ b/spot-contracts/contracts/_utils/BondHelpers.sol @@ -10,6 +10,7 @@ import { UnacceptableDeposit, UnacceptableTrancheLength } from "../_interfaces/P import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; import { BondTranches } from "./BondTranchesHelpers.sol"; +import { ERC20Helpers } from "./ERC20Helpers.sol"; /** * @title BondHelpers @@ -20,6 +21,7 @@ import { BondTranches } from "./BondTranchesHelpers.sol"; library BondHelpers { using SafeCastUpgradeable for uint256; using MathUpgradeable for uint256; + using ERC20Helpers for IERC20Upgradeable; // Replicating value used here: // https://github.com/buttonwood-protocol/tranche/blob/main/contracts/BondController.sol @@ -97,4 +99,17 @@ library BondHelpers { return tranchesOut; } + + /// @notice Helper function which approves underlying tokens and mints tranche tokens by depositing into the provided bond contract. + /// @return The array of tranche tokens minted. + function approveAndDeposit( + IBondController b, + IERC20Upgradeable underlying_, + uint256 underlyingAmt + ) internal returns (ITranche[2] memory) { + BondTranches memory bt = getTranches(b); + underlying_.checkAndApproveMax(address(b), underlyingAmt); + b.deposit(underlyingAmt); + return bt.tranches; + } } diff --git a/spot-contracts/contracts/_utils/ERC20Helpers.sol b/spot-contracts/contracts/_utils/ERC20Helpers.sol new file mode 100644 index 00000000..d73becbc --- /dev/null +++ b/spot-contracts/contracts/_utils/ERC20Helpers.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.20; + +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; + +/** + * @title ERC20Helpers + * + * @notice Library with helper functions for ERC20 Tokens. + * + */ +library ERC20Helpers { + using SafeERC20Upgradeable for IERC20Upgradeable; + + /// @notice Checks if the spender has sufficient allowance. If not, approves the maximum possible amount. + function checkAndApproveMax(IERC20Upgradeable token, address spender, uint256 amount) internal { + uint256 allowance = token.allowance(address(this), spender); + if (allowance < amount) { + token.safeApprove(spender, type(uint256).max); + } + } +} diff --git a/spot-contracts/contracts/_utils/TrancheManager.sol b/spot-contracts/contracts/_utils/TrancheManager.sol new file mode 100644 index 00000000..e4035999 --- /dev/null +++ b/spot-contracts/contracts/_utils/TrancheManager.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.20; + +import { IERC20Upgradeable, IBondController, ITranche } from "../_interfaces/IPerpetualTranche.sol"; + +import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { BondTranches, BondTranchesHelpers } from "./BondTranchesHelpers.sol"; +import { TrancheHelpers } from "./TrancheHelpers.sol"; +import { BondHelpers } from "./BondHelpers.sol"; +import { ERC20Helpers } from "./ERC20Helpers.sol"; + +/** + * @title TrancheManager + * + * @notice Linked external library with helper functions for tranche management. + * + * @dev Proxies which use external libraries are by default NOT upgrade safe. + * We guarantee that this linked external library will never trigger selfdestruct, + * and this one is. + * + */ +library TrancheManager { + // data handling + using BondHelpers for IBondController; + using TrancheHelpers for ITranche; + using BondTranchesHelpers for BondTranches; + + // ERC20 operations + using SafeERC20Upgradeable for IERC20Upgradeable; + using ERC20Helpers for IERC20Upgradeable; + + // math + using MathUpgradeable for uint256; + + //-------------------------------------------------------------------------- + // Helper methods + + /// @notice Low level method that redeems the given mature tranche for the underlying asset. + /// It interacts with the button-wood bond contract. + function execMatureTrancheRedemption(IBondController bond, ITranche tranche, uint256 amount) external { + if (!bond.isMature()) { + bond.mature(); + } + bond.redeemMature(address(tranche), amount); + } + + /// @notice Low level method that redeems the given tranche for the underlying asset, before maturity. + /// If the contract holds sibling tranches with proportional balances, those will also get redeemed. + /// It interacts with the button-wood bond contract. + function execImmatureTrancheRedemption(IBondController bond, BondTranches memory bt) external { + uint256[] memory trancheAmts = bt.computeRedeemableTrancheAmounts(address(this)); + + // NOTE: It is guaranteed that if one tranche amount is zero, all amounts are zeros. + if (trancheAmts[0] > 0) { + bond.redeem(trancheAmts); + } + } + + /// @notice Computes the value of the given amount of tranche tokens, based on it's current CDR. + /// Value is denominated in the underlying collateral. + function computeTrancheValue( + address tranche, + address collateralToken, + uint256 trancheAmt + ) external view returns (uint256) { + (uint256 trancheClaim, uint256 trancheSupply) = ITranche(tranche).getTrancheCollateralization( + IERC20Upgradeable(collateralToken) + ); + return trancheClaim.mulDiv(trancheAmt, trancheSupply, MathUpgradeable.Rounding.Up); + } +} diff --git a/spot-contracts/hardhat.config.ts b/spot-contracts/hardhat.config.ts index e867457b..2d469813 100644 --- a/spot-contracts/hardhat.config.ts +++ b/spot-contracts/hardhat.config.ts @@ -53,7 +53,7 @@ export default { settings: { optimizer: { enabled: true, - runs: 250, + runs: 200, }, }, }, diff --git a/spot-contracts/package.json b/spot-contracts/package.json index e12f8e8d..5e4483df 100644 --- a/spot-contracts/package.json +++ b/spot-contracts/package.json @@ -54,7 +54,7 @@ "ethers": "^6.6.0", "ethers-v5": "npm:ethers@^5.7.0", "ganache-cli": "latest", - "hardhat": "^2.22.19", + "hardhat": "^2.23.0", "hardhat-gas-reporter": "latest", "lodash": "^4.17.21", "prettier": "^2.7.1", diff --git a/spot-contracts/test/FeePolicy.ts b/spot-contracts/test/FeePolicy.ts index 6c99608d..4cf71483 100644 --- a/spot-contracts/test/FeePolicy.ts +++ b/spot-contracts/test/FeePolicy.ts @@ -652,4 +652,72 @@ describe("FeePolicy", function () { }); }); }); + + describe("#computeDREquilibriumSplit", async function () { + it("should compute correct perp and vault underlying amounts", async function () { + const r = await feePolicy.computeDREquilibriumSplit(toFixedPtAmt("100"), 333); + expect(r[0]).to.eq(toFixedPtAmt("24.981245311327831957")); + expect(r[1]).to.eq(toFixedPtAmt("75.018754688672168043")); + }); + + it("should compute correct perp and vault underlying amounts", async function () { + await feePolicy.updateTargetSubscriptionRatio(toPercFixedPtAmt("1.0")); + const r = await feePolicy.computeDREquilibriumSplit(toFixedPtAmt("100"), 500); + expect(r[0]).to.eq(toFixedPtAmt("50")); + expect(r[1]).to.eq(toFixedPtAmt("50")); + }); + + it("should compute correct perp and vault underlying amounts", async function () { + await feePolicy.updateTargetSubscriptionRatio(toPercFixedPtAmt("2.0")); + const r = await feePolicy.computeDREquilibriumSplit(toFixedPtAmt("100"), 500); + expect(r[0]).to.eq(toFixedPtAmt("33.333333333333333333")); + expect(r[1]).to.eq(toFixedPtAmt("66.666666666666666667")); + }); + }); + + describe("#computeDRNeutralSplit", async function () { + it("should compute proportional split", async function () { + const r = await feePolicy.computeDRNeutralSplit( + toFixedPtAmt("1000"), + toFixedPtAmt("100"), + toFixedPtAmt("1000"), + toFixedPtAmt("1000"), + ); + expect(r[0]).to.equal(toFixedPtAmt("100")); + expect(r[1]).to.equal(toFixedPtAmt("100")); + }); + + it("should compute proportional split", async function () { + const r = await feePolicy.computeDRNeutralSplit( + toFixedPtAmt("1000"), + toFixedPtAmt("100"), + toFixedPtAmt("1000"), + toFixedPtAmt("100"), + ); + expect(r[0]).to.equal(toFixedPtAmt("1000")); + expect(r[1]).to.equal(toFixedPtAmt("100")); + }); + + it("should compute proportional split", async function () { + const r = await feePolicy.computeDRNeutralSplit( + toFixedPtAmt("1000"), + toFixedPtAmt("100"), + toFixedPtAmt("100000"), + toFixedPtAmt("100"), + ); + expect(r[0]).to.equal(toFixedPtAmt("1000")); + expect(r[1]).to.equal(toFixedPtAmt("1")); + }); + + it("should compute proportional split", async function () { + const r = await feePolicy.computeDRNeutralSplit( + toFixedPtAmt("1000"), + toFixedPtAmt("100"), + toFixedPtAmt("1000"), + toFixedPtAmt("10000"), + ); + expect(r[0]).to.equal(toFixedPtAmt("10")); + expect(r[1]).to.equal(toFixedPtAmt("100")); + }); + }); }); diff --git a/spot-contracts/test/RouterV2.ts b/spot-contracts/test/RouterV2.ts index 0e3155c9..f7db85c1 100644 --- a/spot-contracts/test/RouterV2.ts +++ b/spot-contracts/test/RouterV2.ts @@ -64,7 +64,14 @@ describe("RouterV2", function () { await perp.updateTolerableTrancheMaturity(600, 3600); await advancePerpQueue(perp, 3600); - vault = new DMock(await ethers.getContractFactory("RolloverVault")); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + vault = new DMock(RolloverVault); await vault.deploy(); await vault.mockMethod("getTVL()", [0]); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/perp/PerpetualTranche.ts b/spot-contracts/test/perp/PerpetualTranche.ts index 382ed96d..ff2232f6 100644 --- a/spot-contracts/test/perp/PerpetualTranche.ts +++ b/spot-contracts/test/perp/PerpetualTranche.ts @@ -56,7 +56,14 @@ describe("PerpetualTranche", function () { }, ); - const vault = new DMock(await ethers.getContractFactory("RolloverVault")); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + const vault = new DMock(RolloverVault); await vault.deploy(); await vault.mockMethod("getTVL()", [0]); await perp.updateVault(vault.target); @@ -213,7 +220,14 @@ describe("PerpetualTranche", function () { describe("when vault reference is set", function () { let tx: Transaction, vault: Contract; beforeEach(async function () { - vault = new DMock(await ethers.getContractFactory("RolloverVault")); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + vault = new DMock(RolloverVault); await vault.deploy(); await vault.mockMethod("getTVL()", [0]); diff --git a/spot-contracts/test/perp/PerpetualTranche_deposit.ts b/spot-contracts/test/perp/PerpetualTranche_deposit.ts index 12db609c..1f6999a5 100644 --- a/spot-contracts/test/perp/PerpetualTranche_deposit.ts +++ b/spot-contracts/test/perp/PerpetualTranche_deposit.ts @@ -65,7 +65,14 @@ describe("PerpetualTranche", function () { ); await advancePerpQueue(perp, 3600); - const vault = new DMock(await ethers.getContractFactory("RolloverVault")); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + const vault = new DMock(RolloverVault); await vault.deploy(); await vault.mockMethod("getTVL()", [0]); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/perp/PerpetualTranche_redeem.ts b/spot-contracts/test/perp/PerpetualTranche_redeem.ts index be4c1d12..3df601c2 100644 --- a/spot-contracts/test/perp/PerpetualTranche_redeem.ts +++ b/spot-contracts/test/perp/PerpetualTranche_redeem.ts @@ -66,7 +66,14 @@ describe("PerpetualTranche", function () { ); await advancePerpQueue(perp, 3600); - const vault = new DMock(await ethers.getContractFactory("RolloverVault")); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + const vault = new DMock(RolloverVault); await vault.deploy(); await vault.mockMethod("getTVL()", [0]); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/rollover-vault/RolloverVault.ts b/spot-contracts/test/rollover-vault/RolloverVault.ts index aecd46e6..4cd71e1e 100644 --- a/spot-contracts/test/rollover-vault/RolloverVault.ts +++ b/spot-contracts/test/rollover-vault/RolloverVault.ts @@ -15,6 +15,7 @@ import { } from "../helpers"; let vault: Contract, + trancheManager: Contract, perp: Contract, feePolicy: Contract, collateralToken: Contract, @@ -47,8 +48,17 @@ describe("RolloverVault", function () { await feePolicy.mockMethod("computeUnderlyingToPerpVaultSwapFeePerc(uint256,uint256)", [0]); await feePolicy.mockMethod("computePerpToUnderlyingVaultSwapFeePerc(uint256,uint256)", [0]); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await collateralToken.approve(vault.target, toFixedPtAmt("1")); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.mockMethod("vault()", [vault.target]); @@ -285,8 +295,14 @@ describe("RolloverVault", function () { await perp.updateTolerableTrancheMaturity(1200, 4800); await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/rollover-vault/RolloverVault_deploy.ts b/spot-contracts/test/rollover-vault/RolloverVault_deploy.ts index bf9bdad6..0b199a67 100644 --- a/spot-contracts/test/rollover-vault/RolloverVault_deploy.ts +++ b/spot-contracts/test/rollover-vault/RolloverVault_deploy.ts @@ -76,8 +76,17 @@ describe("RolloverVault", function () { await perp.updateTolerableTrancheMaturity(1200, 4800); await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/rollover-vault/RolloverVault_deposit_redeem.ts b/spot-contracts/test/rollover-vault/RolloverVault_deposit_redeem.ts index c236618c..198bbb68 100644 --- a/spot-contracts/test/rollover-vault/RolloverVault_deposit_redeem.ts +++ b/spot-contracts/test/rollover-vault/RolloverVault_deposit_redeem.ts @@ -83,8 +83,17 @@ describe("RolloverVault", function () { await perp.updateTolerableTrancheMaturity(1200, 4800); await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/rollover-vault/RolloverVault_pair_ops.ts b/spot-contracts/test/rollover-vault/RolloverVault_pair_ops.ts new file mode 100644 index 00000000..5820768b --- /dev/null +++ b/spot-contracts/test/rollover-vault/RolloverVault_pair_ops.ts @@ -0,0 +1,587 @@ +import { expect } from "chai"; +import { network, ethers, upgrades } from "hardhat"; +import { Contract, Signer } from "ethers"; + +import { + setupCollateralToken, + mintCollteralToken, + setupBondFactory, + depositIntoBond, + bondAt, + getTranches, + toFixedPtAmt, + toPercFixedPtAmt, + getDepositBond, + advancePerpQueue, + advancePerpQueueToBondMaturity, + checkPerpComposition, + checkVaultComposition, + DMock, +} from "../helpers"; + +let vault: Contract; +let perp: Contract; +let bondFactory: Contract; +let collateralToken: Contract; +let issuer: Contract; +let feePolicy: Contract; +let deployer: Signer; +let reserveTranches: Contract[][] = []; +let remainingJuniorTranches: Contract[][] = []; +let currentBondIn: Contract; +let currentTranchesIn: Contract[]; + +describe("RolloverVault", function () { + beforeEach(async function () { + await network.provider.send("hardhat_reset"); + + const accounts = await ethers.getSigners(); + deployer = accounts[0]; + + bondFactory = await setupBondFactory(); + ({ collateralToken } = await setupCollateralToken("Bitcoin", "BTC")); + + const BondIssuer = await ethers.getContractFactory("BondIssuer"); + issuer = await upgrades.deployProxy( + BondIssuer.connect(deployer), + [bondFactory.target, collateralToken.target, 4800, [200, 800], 1200, 0], + { + initializer: "init(address,address,uint256,uint256[],uint256,uint256)", + }, + ); + + feePolicy = new DMock(await ethers.getContractFactory("FeePolicy")); + await feePolicy.deploy(); + await feePolicy.mockMethod("decimals()", [8]); + await feePolicy.mockMethod("computeDeviationRatio((uint256,uint256,uint256))", [toPercFixedPtAmt("1")]); + await feePolicy.mockMethod("computePerpMintFeePerc()", [0]); + await feePolicy.mockMethod("computePerpBurnFeePerc()", [0]); + + await feePolicy.mockMethod("computeVaultMintFeePerc()", [0]); + await feePolicy.mockMethod("computeVaultBurnFeePerc()", [0]); + await feePolicy.mockMethod("computeUnderlyingToPerpVaultSwapFeePerc(uint256,uint256)", [0]); + await feePolicy.mockMethod("computePerpToUnderlyingVaultSwapFeePerc(uint256,uint256)", [0]); + + const PerpetualTranche = await ethers.getContractFactory("PerpetualTranche"); + perp = await upgrades.deployProxy( + PerpetualTranche.connect(deployer), + ["PerpetualTranche", "PERP", collateralToken.target, issuer.target, feePolicy.target], + { + initializer: "init(string,string,address,address,address)", + }, + ); + await perp.updateTolerableTrancheMaturity(1200, 4800); + await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); + + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); + await collateralToken.approve(vault.target, toFixedPtAmt("1")); + await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); + await perp.updateVault(vault.target); + + reserveTranches = []; + remainingJuniorTranches = []; + for (let i = 0; i < 4; i++) { + const bond = await getDepositBond(perp); + const tranches = await getTranches(bond); + await depositIntoBond(bond, toFixedPtAmt("1200"), deployer); + + await tranches[0].approve(perp.target, toFixedPtAmt("200")); + await perp.deposit(tranches[0].target, toFixedPtAmt("200")); + + reserveTranches.push(tranches[0]); + remainingJuniorTranches.push(tranches[1]); + await advancePerpQueue(perp, 1200); + } + + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3)], + [toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("200")], + ); + await checkVaultComposition(vault, [collateralToken], [0]); + expect(await vault.assetCount()).to.eq(1); + + await mintCollteralToken(collateralToken, toFixedPtAmt("100000"), deployer); + currentBondIn = await bondAt(await perp.getDepositBond.staticCall()); + currentTranchesIn = await getTranches(currentBondIn); + + await collateralToken.approve(vault.target, toFixedPtAmt("10000")); + await perp.approve(vault.target, toFixedPtAmt("10000")); + + await vault.deposit(toFixedPtAmt("1000")); + await vault.deploy(); + await vault.deposit(toFixedPtAmt("1000")); + + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("1200"), toFixedPtAmt("800")], + ); + expect(await vault.assetCount()).to.eq(2); + }); + + afterEach(async function () { + await network.provider.send("hardhat_reset"); + }); + + describe("#mint2", function () { + describe("when dr = 1", function () { + beforeEach(async function () { + await feePolicy.mockMethod("computeDREquilibriumSplit(uint256,uint256)", [ + toFixedPtAmt("25"), + toFixedPtAmt("75"), + ]); + }); + + it("should compute amounts", async function () { + const r = await vault.mint2.staticCall(toFixedPtAmt("100")); + expect(r[0]).to.eq(toFixedPtAmt("25")); + expect(r[1]).to.eq(toFixedPtAmt("75") * 1000000n); + }); + + it("should transfer underlying", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + collateralToken, + [deployer], + [toFixedPtAmt("-100")], + ); + }); + + it("should mint perps", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + perp, + [deployer], + [toFixedPtAmt("25")], + ); + }); + + it("should mint vault notes", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + vault, + [deployer], + [toFixedPtAmt("75") * 1000000n], + ); + }); + + it("should increase tvl", async function () { + await vault.mint2(toFixedPtAmt("100")); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("2075")); + expect(await perp.getTVL.staticCall()).to.eq(toFixedPtAmt("825")); + }); + + it("should have the updated composition", async function () { + await vault.mint2(toFixedPtAmt("100")); + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3), currentTranchesIn[0]], + [toFixedPtAmt("0"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("225")], + ); + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("1175"), toFixedPtAmt("900")], + ); + }); + + it("should sync vault assets", async function () { + const tx = vault.mint2(toFixedPtAmt("100")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(collateralToken.target, toFixedPtAmt("1175")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(currentTranchesIn[1].target, toFixedPtAmt("900")); + }); + }); + + describe("when dr > 1", function () { + beforeEach(async function () { + await feePolicy.mockMethod("computeDREquilibriumSplit(uint256,uint256)", [ + toFixedPtAmt("25"), + toFixedPtAmt("75"), + ]); + await feePolicy.mockMethod("computeDeviationRatio((uint256,uint256,uint256))", [toPercFixedPtAmt("1.25")]); + await vault.deposit(toFixedPtAmt("1000")); + }); + + it("should compute amounts", async function () { + const r = await vault.mint2.staticCall(toFixedPtAmt("100")); + expect(r[0]).to.eq(toFixedPtAmt("25")); + expect(r[1]).to.eq(toFixedPtAmt("75") * 1000000n); + }); + + it("should transfer underlying", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + collateralToken, + [deployer], + [toFixedPtAmt("-100")], + ); + }); + + it("should mint perps", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + perp, + [deployer], + [toFixedPtAmt("25")], + ); + }); + + it("should mint vault notes", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + vault, + [deployer], + [toFixedPtAmt("75") * 1000000n], + ); + }); + + it("should increase tvl", async function () { + await vault.mint2(toFixedPtAmt("100")); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("3075")); + expect(await perp.getTVL.staticCall()).to.eq(toFixedPtAmt("825")); + }); + + it("should have the updated composition", async function () { + await vault.mint2(toFixedPtAmt("100")); + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3), currentTranchesIn[0]], + [toFixedPtAmt("0"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("225")], + ); + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("2175"), toFixedPtAmt("900")], + ); + }); + + it("should sync vault assets", async function () { + const tx = vault.mint2(toFixedPtAmt("100")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(collateralToken.target, toFixedPtAmt("2175")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(currentTranchesIn[1].target, toFixedPtAmt("900")); + }); + }); + + describe("when dr < 1", function () { + beforeEach(async function () { + await feePolicy.mockMethod("computeDREquilibriumSplit(uint256,uint256)", [ + toFixedPtAmt("25"), + toFixedPtAmt("75"), + ]); + await feePolicy.mockMethod("computeDeviationRatio((uint256,uint256,uint256))", [toPercFixedPtAmt("0.75")]); + await vault.redeem(toFixedPtAmt("500") * 1000000n); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("1500")); + }); + + it("should compute amounts", async function () { + const r = await vault.mint2.staticCall(toFixedPtAmt("100")); + expect(r[0]).to.eq(toFixedPtAmt("25")); + expect(r[1]).to.eq(toFixedPtAmt("75") * 1000000n); + }); + + it("should transfer underlying", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + collateralToken, + [deployer], + [toFixedPtAmt("-100")], + ); + }); + + it("should mint perps", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + perp, + [deployer], + [toFixedPtAmt("25")], + ); + }); + + it("should mint vault notes", async function () { + await expect(() => vault.mint2(toFixedPtAmt("100"))).to.changeTokenBalances( + vault, + [deployer], + [toFixedPtAmt("75") * 1000000n], + ); + }); + + it("should increase tvl", async function () { + await vault.mint2(toFixedPtAmt("100")); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("1575")); + expect(await perp.getTVL.staticCall()).to.eq(toFixedPtAmt("825")); + }); + + it("should have the updated composition", async function () { + await vault.mint2(toFixedPtAmt("100")); + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3), currentTranchesIn[0]], + [toFixedPtAmt("0"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("200"), toFixedPtAmt("225")], + ); + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("875"), toFixedPtAmt("700")], + ); + }); + + it("should sync vault assets", async function () { + const tx = vault.mint2(toFixedPtAmt("100")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(collateralToken.target, toFixedPtAmt("875")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(currentTranchesIn[1].target, toFixedPtAmt("700")); + }); + }); + }); + + describe("#redeem2", function () { + describe("when redeeming proportionally", function () { + beforeEach(async function () { + await feePolicy.mockMethod("computeDRNeutralSplit(uint256,uint256,uint256,uint256)", [ + toFixedPtAmt("25"), + toFixedPtAmt("75") * 1000000n, + ]); + }); + + it("should compute amounts", async function () { + const r = await vault.redeem2.staticCall(toFixedPtAmt("25"), toFixedPtAmt("75") * 1000000n); + + expect(r[0]).to.eq(toFixedPtAmt("25")); + expect(r[1]).to.eq(toFixedPtAmt("75") * 1000000n); + + expect(r[2][0][0]).to.eq(collateralToken.target); + expect(r[2][0][1]).to.eq(toFixedPtAmt("45")); + + expect(r[2][1][0]).to.eq(reserveTranches[3].target); + expect(r[2][1][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][2][0]).to.eq(reserveTranches[1].target); + expect(r[2][2][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][3][0]).to.eq(reserveTranches[2].target); + expect(r[2][3][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][4][0]).to.eq(currentTranchesIn[0].target); + expect(r[2][4][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][5][0]).to.eq(currentTranchesIn[1].target); + expect(r[2][5][1]).to.eq(toFixedPtAmt("30")); + }); + + it("should burn perps", async function () { + await expect(() => vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("75") * 1000000n)).to.changeTokenBalances( + perp, + [deployer], + [toFixedPtAmt("-25")], + ); + }); + + it("should burn vault notes", async function () { + await expect(() => vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("75") * 1000000n)).to.changeTokenBalances( + vault, + [deployer], + [toFixedPtAmt("-75") * 1000000n], + ); + }); + + it("should decrease tvl", async function () { + await vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("75") * 1000000n); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("1925")); + expect(await perp.getTVL.staticCall()).to.eq(toFixedPtAmt("775")); + }); + + it("should have the updated composition", async function () { + await vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("75") * 1000000n); + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3), currentTranchesIn[0]], + [ + toFixedPtAmt("0"), + toFixedPtAmt("193.75"), + toFixedPtAmt("193.75"), + toFixedPtAmt("193.75"), + toFixedPtAmt("193.75"), + ], + ); + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("1155"), toFixedPtAmt("770")], + ); + }); + + it("should sync vault assets", async function () { + const tx = vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("75") * 1000000n); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(collateralToken.target, toFixedPtAmt("1155")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(currentTranchesIn[1].target, toFixedPtAmt("770")); + }); + }); + + describe("when redeeming more perps", function () { + beforeEach(async function () { + await feePolicy.mockMethod("computeDRNeutralSplit(uint256,uint256,uint256,uint256)", [ + toFixedPtAmt("50"), + toFixedPtAmt("75") * 1000000n, + ]); + await collateralToken.transfer(perp.target, toFixedPtAmt("100")); + }); + + it("should compute amounts", async function () { + const r = await vault.redeem2.staticCall(toFixedPtAmt("100"), toFixedPtAmt("75") * 1000000n); + + expect(r[0]).to.eq(toFixedPtAmt("50")); + expect(r[1]).to.eq(toFixedPtAmt("75") * 1000000n); + + expect(r[2][0][0]).to.eq(collateralToken.target); + expect(r[2][0][1]).to.eq(toFixedPtAmt("51.25")); + + expect(r[2][1][0]).to.eq(reserveTranches[3].target); + expect(r[2][1][1]).to.eq(toFixedPtAmt("12.5")); + + expect(r[2][2][0]).to.eq(reserveTranches[1].target); + expect(r[2][2][1]).to.eq(toFixedPtAmt("12.5")); + + expect(r[2][3][0]).to.eq(reserveTranches[2].target); + expect(r[2][3][1]).to.eq(toFixedPtAmt("12.5")); + + expect(r[2][4][0]).to.eq(currentTranchesIn[0].target); + expect(r[2][4][1]).to.eq(toFixedPtAmt("12.5")); + + expect(r[2][5][0]).to.eq(currentTranchesIn[1].target); + expect(r[2][5][1]).to.eq(toFixedPtAmt("30")); + }); + + it("should burn perps", async function () { + await expect(() => vault.redeem2(toFixedPtAmt("100"), toFixedPtAmt("75") * 1000000n)).to.changeTokenBalances( + perp, + [deployer], + [toFixedPtAmt("-50")], + ); + }); + + it("should burn vault notes", async function () { + await expect(() => vault.redeem2(toFixedPtAmt("100"), toFixedPtAmt("75") * 1000000n)).to.changeTokenBalances( + vault, + [deployer], + [toFixedPtAmt("-75") * 1000000n], + ); + }); + + it("should decrease tvl", async function () { + await vault.redeem2(toFixedPtAmt("100"), toFixedPtAmt("75") * 1000000n); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("1925")); + expect(await perp.getTVL.staticCall()).to.eq(toFixedPtAmt("843.75")); + }); + + it("should have the updated composition", async function () { + await vault.redeem2(toFixedPtAmt("100"), toFixedPtAmt("75") * 1000000n); + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3), currentTranchesIn[0]], + [ + toFixedPtAmt("93.75"), + toFixedPtAmt("187.5"), + toFixedPtAmt("187.5"), + toFixedPtAmt("187.5"), + toFixedPtAmt("187.5"), + ], + ); + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("1155"), toFixedPtAmt("770")], + ); + }); + + it("should sync vault assets", async function () { + const tx = vault.redeem2(toFixedPtAmt("100"), toFixedPtAmt("75") * 1000000n); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(collateralToken.target, toFixedPtAmt("1155")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(currentTranchesIn[1].target, toFixedPtAmt("770")); + }); + }); + + describe("when redeeming more vault notes", function () { + beforeEach(async function () { + await feePolicy.mockMethod("computeDRNeutralSplit(uint256,uint256,uint256,uint256)", [ + toFixedPtAmt("25"), + toFixedPtAmt("150") * 1000000n, + ]); + }); + + it("should compute amounts", async function () { + const r = await vault.redeem2.staticCall(toFixedPtAmt("25"), toFixedPtAmt("250") * 1000000n); + + expect(r[0]).to.eq(toFixedPtAmt("25")); + expect(r[1]).to.eq(toFixedPtAmt("150") * 1000000n); + + expect(r[2][0][0]).to.eq(collateralToken.target); + expect(r[2][0][1]).to.eq(toFixedPtAmt("90")); + + expect(r[2][1][0]).to.eq(reserveTranches[3].target); + expect(r[2][1][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][2][0]).to.eq(reserveTranches[1].target); + expect(r[2][2][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][3][0]).to.eq(reserveTranches[2].target); + expect(r[2][3][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][4][0]).to.eq(currentTranchesIn[0].target); + expect(r[2][4][1]).to.eq(toFixedPtAmt("6.25")); + + expect(r[2][5][0]).to.eq(currentTranchesIn[1].target); + expect(r[2][5][1]).to.eq(toFixedPtAmt("60")); + }); + + it("should burn perps", async function () { + await expect(() => vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("250") * 1000000n)).to.changeTokenBalances( + perp, + [deployer], + [toFixedPtAmt("-25")], + ); + }); + + it("should burn vault notes", async function () { + await expect(() => vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("250") * 1000000n)).to.changeTokenBalances( + vault, + [deployer], + [toFixedPtAmt("-150") * 1000000n], + ); + }); + + it("should decrease tvl", async function () { + await vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("250") * 1000000n); + expect(await vault.getTVL.staticCall()).to.eq(toFixedPtAmt("1850")); + expect(await perp.getTVL.staticCall()).to.eq(toFixedPtAmt("775")); + }); + + it("should have the updated composition", async function () { + await vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("250") * 1000000n); + await checkPerpComposition( + perp, + [collateralToken, ...reserveTranches.slice(-3), currentTranchesIn[0]], + [ + toFixedPtAmt("0"), + toFixedPtAmt("193.75"), + toFixedPtAmt("193.75"), + toFixedPtAmt("193.75"), + toFixedPtAmt("193.75"), + ], + ); + await checkVaultComposition( + vault, + [collateralToken, currentTranchesIn[1]], + [toFixedPtAmt("1110"), toFixedPtAmt("740")], + ); + }); + + it("should sync vault assets", async function () { + const tx = vault.redeem2(toFixedPtAmt("25"), toFixedPtAmt("250") * 1000000n); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(collateralToken.target, toFixedPtAmt("1110")); + await expect(tx).to.emit(vault, "AssetSynced").withArgs(currentTranchesIn[1].target, toFixedPtAmt("740")); + }); + }); + }); +}); diff --git a/spot-contracts/test/rollover-vault/RolloverVault_rebalance.ts b/spot-contracts/test/rollover-vault/RolloverVault_rebalance.ts index fce7decc..f087499e 100644 --- a/spot-contracts/test/rollover-vault/RolloverVault_rebalance.ts +++ b/spot-contracts/test/rollover-vault/RolloverVault_rebalance.ts @@ -73,8 +73,17 @@ describe("RolloverVault", function () { await perp.updateTolerableTrancheMaturity(86400, 4 * 86400); await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/rollover-vault/RolloverVault_recover.ts b/spot-contracts/test/rollover-vault/RolloverVault_recover.ts index e754fe8c..ddcec163 100644 --- a/spot-contracts/test/rollover-vault/RolloverVault_recover.ts +++ b/spot-contracts/test/rollover-vault/RolloverVault_recover.ts @@ -75,8 +75,17 @@ describe("RolloverVault", function () { await perp.updateTolerableTrancheMaturity(1200, 4800); await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.updateVault(vault.target); diff --git a/spot-contracts/test/rollover-vault/RolloverVault_swap.ts b/spot-contracts/test/rollover-vault/RolloverVault_swap.ts index d066e52c..d1d299a8 100644 --- a/spot-contracts/test/rollover-vault/RolloverVault_swap.ts +++ b/spot-contracts/test/rollover-vault/RolloverVault_swap.ts @@ -75,8 +75,17 @@ describe("RolloverVault", function () { await perp.updateTolerableTrancheMaturity(1200, 4800); await advancePerpQueueToBondMaturity(perp, await getDepositBond(perp)); - const RolloverVault = await ethers.getContractFactory("RolloverVault"); - vault = await upgrades.deployProxy(RolloverVault.connect(deployer)); + const TrancheManager = await ethers.getContractFactory("TrancheManager"); + const trancheManager = await TrancheManager.deploy(); + const RolloverVault = await ethers.getContractFactory("RolloverVault", { + libraries: { + TrancheManager: trancheManager.target, + }, + }); + await upgrades.silenceWarnings(); + vault = await upgrades.deployProxy(RolloverVault.connect(deployer), { + unsafeAllow: ["external-library-linking"], + }); await collateralToken.approve(vault.target, toFixedPtAmt("1")); await vault.init("RolloverVault", "VSHARE", perp.target, feePolicy.target); await perp.updateVault(vault.target); diff --git a/yarn.lock b/yarn.lock index d219249f..17ed29ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -59,7 +59,7 @@ __metadata: ethers: ^6.6.0 ethers-v5: "npm:ethers@^5.7.0" ganache-cli: latest - hardhat: ^2.22.19 + hardhat: ^2.23.0 hardhat-gas-reporter: latest lodash: ^4.17.21 prettier: ^2.7.1 @@ -435,6 +435,15 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/rlp@npm:^5.0.2": + version: 5.0.2 + resolution: "@ethereumjs/rlp@npm:5.0.2" + bin: + rlp: bin/rlp.cjs + checksum: b569061ddb1f4cf56a82f7a677c735ba37f9e94e2bbaf567404beb9e2da7aa1f595e72fc12a17c61f7aec67fd5448443efe542967c685a2fe0ffc435793dcbab + languageName: node + linkType: hard + "@ethereumjs/tx@npm:3.4.0": version: 3.4.0 resolution: "@ethereumjs/tx@npm:3.4.0" @@ -466,6 +475,16 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/util@npm:^9.1.0": + version: 9.1.0 + resolution: "@ethereumjs/util@npm:9.1.0" + dependencies: + "@ethereumjs/rlp": ^5.0.2 + ethereum-cryptography: ^2.2.1 + checksum: 594e009c3001ca1ca658b4ded01b38e72f5dd5dd76389efd90cb020de099176a3327685557df268161ac3144333cfe8abaae68cda8ae035d9cc82409d386d79a + languageName: node + linkType: hard + "@ethereumjs/vm@npm:5.6.0": version: 5.6.0 resolution: "@ethereumjs/vm@npm:5.6.0" @@ -1133,6 +1152,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:~1.8.1": + version: 1.8.1 + resolution: "@noble/curves@npm:1.8.1" + dependencies: + "@noble/hashes": 1.7.1 + checksum: 4143f1248ed57c1ae46dfef5c692a91383e5830420b9c72d3ff1061aa9ebbf8999297da6d2aed8a9716fef8e6b1f5a45737feeab02abf55ca2a4f514bf9339ec + languageName: node + linkType: hard + "@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": version: 1.2.0 resolution: "@noble/hashes@npm:1.2.0" @@ -1154,6 +1182,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.7.1, @noble/hashes@npm:~1.7.1": + version: 1.7.1 + resolution: "@noble/hashes@npm:1.7.1" + checksum: 4f1b56428a10323feef17e4f437c9093556cb18db06f94d254043fadb69c3da8475f96eb3f8322d41e8670117d7486475a8875e68265c2839f60fd03edd6a616 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.2": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" @@ -1195,6 +1230,13 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/edr-darwin-arm64@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.10.0" + checksum: 36fed73ecffa04931b7be27bf9b835044bf3d4ec4a65271c786416bd021e8bf44d457d5cfced5dbe1e64a83558ac861d048e2ac43807726802fc231be99d5c87 + languageName: node + linkType: hard + "@nomicfoundation/edr-darwin-arm64@npm:0.7.0": version: 0.7.0 resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.7.0" @@ -1202,10 +1244,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-arm64@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.8.0" - checksum: efae7cf00c6c18e9b3854d8bf3fbb4b1e3932279f83fabb82ddfd74d623886d3cf6b00d4c437b5e81e51d16d02a5759f5a27dae25e0d64eec33422df43251d16 +"@nomicfoundation/edr-darwin-x64@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.10.0" + checksum: 3317f2c79e90198a1387e436ca403578c70281e829f88b8535d408766e7b47c96bdb705ce2d068786ac9a44a45e5bbd90d13c6206aea36ee43e0d1b49a10f3ae languageName: node linkType: hard @@ -1216,10 +1258,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-x64@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-darwin-x64@npm:0.8.0" - checksum: fdc298850dd83e4c6e9ca42b87de112dfd38a369b4f8d7b2fbeca6e6e3f1d24879bc6e57e2362eb173525c26bd9456621c95fd4209feb1d17e3b724d8d9ed232 +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.10.0" + checksum: 4cbbf825b53600b1089bb8d3fe25e1cf55808484d1bbd003073012471fb4d38668bdab2226d879d66d8c3d554d361d51f3a3e6ee90b708dbe28c497f30da389c languageName: node linkType: hard @@ -1230,10 +1272,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-gnu@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.8.0" - checksum: f1df885d79e7887a7fb67b17e1e2337d7eba65c415942d21f1ca1118265597ba9caec409a3ff226b96a8d2facb7bfc1e1fc1338f831e793621afa25a9e3d78ad +"@nomicfoundation/edr-linux-arm64-musl@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.10.0" + checksum: 638cb8781fbec33bb63451859e41e32c9989efdd1ba68f90f68426cda11c069ac4ffe1962520ef7d61723be05566f671e84fd35594ce2c19199cfb37d89d425f languageName: node linkType: hard @@ -1244,10 +1286,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-musl@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.8.0" - checksum: aa5de26be9d44b9e0e0538e1a5110aa173932cb9a21ab6c60deabe9499399f775cad02353f05ae7b8883fa59af6dd92e0f7a268fb7518736f3134a5d66d8f71c +"@nomicfoundation/edr-linux-x64-gnu@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.10.0" + checksum: 8f46fbbadd15c52b1582349e640206004506bbe7764161bf5d7cdd47f17b3ccb11929a9946d364bc13e699a2ef5419780ece61c52515a14d47993fc446ab698b languageName: node linkType: hard @@ -1258,10 +1300,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-gnu@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.8.0" - checksum: 0eeda6028fa923a8bd2b46ae65344e7244a4b06817fdb509349802cb80c0a81c7751d2727e71cb780474453d74e9f286807e7efc714db22205c47d3f676d6bb4 +"@nomicfoundation/edr-linux-x64-musl@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.10.0" + checksum: 9d79e15ec478f66028e84c74251e2e5c675bbee286e91aa2c6d6aefc91898de7fba8ba286eae0f4592dcb74ad69193fb9a99515e99314e5e0c6b10b0c539f651 languageName: node linkType: hard @@ -1272,10 +1314,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-musl@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.8.0" - checksum: 87d8d3522f36b4b6023a05c53f7d33748afb432659cafb4e255dc012613757cc3f70687693d080c65ecf8d73092534d4a00959b9d69e9d596dbce230f2e76e87 +"@nomicfoundation/edr-win32-x64-msvc@npm:0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.10.0" + checksum: 7a9214e6a8f4d9eb2df0d7c5a4fc3c32a8ac26d0a96dd2c921e31d6605dfbcd6bbdfed9cb636da027683a30accc985412e964026726d57572456434382d16557 languageName: node linkType: hard @@ -1286,10 +1328,18 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-win32-x64-msvc@npm:0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.8.0" - checksum: 764dbf36654aa377d4b33e751b3cb98381f5dad1124cce0acdc4a0cbc247fe1b51f163ec3b293009937c398cce482bd492d21e00383acbc201c25380f53bb87d +"@nomicfoundation/edr@npm:^0.10.0": + version: 0.10.0 + resolution: "@nomicfoundation/edr@npm:0.10.0" + dependencies: + "@nomicfoundation/edr-darwin-arm64": 0.10.0 + "@nomicfoundation/edr-darwin-x64": 0.10.0 + "@nomicfoundation/edr-linux-arm64-gnu": 0.10.0 + "@nomicfoundation/edr-linux-arm64-musl": 0.10.0 + "@nomicfoundation/edr-linux-x64-gnu": 0.10.0 + "@nomicfoundation/edr-linux-x64-musl": 0.10.0 + "@nomicfoundation/edr-win32-x64-msvc": 0.10.0 + checksum: ede23be8cb2b6a070ff75eed168d00fc63410cd33b43579bd9148d88f1d22f7b6c8d5316e6fb23efda4b07ef933fd92197ba63f28528a975d2372e2ee8d15c1a languageName: node linkType: hard @@ -1308,21 +1358,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr@npm:^0.8.0": - version: 0.8.0 - resolution: "@nomicfoundation/edr@npm:0.8.0" - dependencies: - "@nomicfoundation/edr-darwin-arm64": 0.8.0 - "@nomicfoundation/edr-darwin-x64": 0.8.0 - "@nomicfoundation/edr-linux-arm64-gnu": 0.8.0 - "@nomicfoundation/edr-linux-arm64-musl": 0.8.0 - "@nomicfoundation/edr-linux-x64-gnu": 0.8.0 - "@nomicfoundation/edr-linux-x64-musl": 0.8.0 - "@nomicfoundation/edr-win32-x64-msvc": 0.8.0 - checksum: eeabb9f62174e1742d08d31904ae879c7c05de89f929980df2fa9dec5763528f3b8965bedca93fe7d1d4adb9e56c56a9dc8096992cf6f10b907d3f32f04c6676 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-common@npm:4.0.4": version: 4.0.4 resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.4" @@ -1830,6 +1865,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.2.2": + version: 1.2.4 + resolution: "@scure/base@npm:1.2.4" + checksum: db554eb550a1bd17684af9282e1ad751050a13d4add0e83ad61cc496680d7d1c1c1120ca780e72935a293bb59721c20a006a53a5eec6f6b5bdcd702cf27c8cae + languageName: node + linkType: hard + "@scure/bip32@npm:1.1.5": version: 1.1.5 resolution: "@scure/bip32@npm:1.1.5" @@ -5369,7 +5411,7 @@ __metadata: languageName: node linkType: hard -"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2, ethereum-cryptography@npm:^2.1.3": +"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2, ethereum-cryptography@npm:^2.1.3, ethereum-cryptography@npm:^2.2.1": version: 2.2.1 resolution: "ethereum-cryptography@npm:2.2.1" dependencies: @@ -6493,16 +6535,13 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.22.19": - version: 2.22.19 - resolution: "hardhat@npm:2.22.19" +"hardhat@npm:^2.23.0": + version: 2.23.0 + resolution: "hardhat@npm:2.23.0" dependencies: + "@ethereumjs/util": ^9.1.0 "@ethersproject/abi": ^5.1.2 - "@metamask/eth-sig-util": ^4.0.0 - "@nomicfoundation/edr": ^0.8.0 - "@nomicfoundation/ethereumjs-common": 4.0.4 - "@nomicfoundation/ethereumjs-tx": 5.0.4 - "@nomicfoundation/ethereumjs-util": 9.0.4 + "@nomicfoundation/edr": ^0.10.0 "@nomicfoundation/solidity-analyzer": ^0.1.0 "@sentry/node": ^5.18.1 "@types/bn.js": ^5.1.0 @@ -6517,7 +6556,6 @@ __metadata: enquirer: ^2.3.0 env-paths: ^2.2.0 ethereum-cryptography: ^1.0.3 - ethereumjs-abi: ^0.6.8 find-up: ^5.0.0 fp-ts: 1.19.3 fs-extra: ^7.0.1 @@ -6526,6 +6564,7 @@ __metadata: json-stream-stringify: ^3.1.4 keccak: ^3.0.2 lodash: ^4.17.11 + micro-eth-signer: ^0.14.0 mnemonist: ^0.38.0 mocha: ^10.0.0 p-map: ^4.0.0 @@ -6551,7 +6590,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 08b6c1912528ed3c013d731a312836e22ad5bdbbce27a78f8dc395154548f723c6b13af90dcee71401bfd1ee910005ab252aafb44cc5e42daaee8a41e5b4c739 + checksum: b2814cf5e996547b7a2c8f3b8bd116c34de94c3a1eca869aa5407622c8a93c1dda0b95064b004826df88594a91cbc149fba6f437af4743278790b7ffaf11acdb languageName: node linkType: hard @@ -8248,6 +8287,17 @@ __metadata: languageName: node linkType: hard +"micro-eth-signer@npm:^0.14.0": + version: 0.14.0 + resolution: "micro-eth-signer@npm:0.14.0" + dependencies: + "@noble/curves": ~1.8.1 + "@noble/hashes": ~1.7.1 + micro-packed: ~0.7.2 + checksum: 9f49282ed8d0057c77cb65ee87a7c08909cf25ae62676c9ff8006d804bbd882dd2f56956e8589e3716ec7c647a9f308f45810c1f40416873f352a59941a17d7b + languageName: node + linkType: hard + "micro-ftch@npm:^0.3.1": version: 0.3.1 resolution: "micro-ftch@npm:0.3.1" @@ -8255,6 +8305,15 @@ __metadata: languageName: node linkType: hard +"micro-packed@npm:~0.7.2": + version: 0.7.2 + resolution: "micro-packed@npm:0.7.2" + dependencies: + "@scure/base": ~1.2.2 + checksum: 7b9a102fe245f1902614aadf250e5d1a28caea070ad5b79fef9a449f021c5a5e2030ac3f43de7e657ec46283de0bb892a568024189064b71ecc2e9bc5efee684 + languageName: node + linkType: hard + "micromatch@npm:^4.0.4": version: 4.0.7 resolution: "micromatch@npm:4.0.7"