diff --git a/spot-vaults/contracts/BillBroker.sol b/spot-vaults/contracts/BillBroker.sol index b3d15589..9ecfc298 100644 --- a/spot-vaults/contracts/BillBroker.sol +++ b/spot-vaults/contracts/BillBroker.sol @@ -17,7 +17,7 @@ import { IPerpPricer } from "./_interfaces/IPerpPricer.sol"; import { ReserveState, BillBrokerFees } from "./_interfaces/types/BillBrokerTypes.sol"; import { Line, Range } from "./_interfaces/types/CommonTypes.sol"; import { UnacceptableSwap, UnreliablePrice, UnexpectedDecimals, InvalidPerc, SlippageTooHigh, UnauthorizedCall } from "./_interfaces/errors/CommonErrors.sol"; -import { InvalidARBound, UnexpectedARDelta } from "./_interfaces/errors/BillBrokerErrors.sol"; +import { InvalidARBound } from "./_interfaces/errors/BillBrokerErrors.sol"; /** * @title BillBroker @@ -72,16 +72,20 @@ contract BillBroker is // Math using MathUpgradeable for uint256; - using LineHelpers for Line; + using MathHelpers for uint256; using MathHelpers for int256; + using LineHelpers for Line; //------------------------------------------------------------------------- // Constants & Immutables uint256 public constant DECIMALS = 18; uint256 public constant ONE = (10 ** DECIMALS); + uint256 public constant TWO = ONE * 2; uint256 private constant INITIAL_RATE = 1000000; uint256 public constant MINIMUM_LIQUIDITY = 10 ** 22; + uint256 public constant MAX_FEE_FACTOR = ((ONE * 6) / 5); // 1.2 or 20% + uint256 public constant MAX_ASSET_RATIO = uint256(type(int256).max); //------------------------------------------------------------------------- // Storage @@ -113,7 +117,7 @@ contract BillBroker is Range public arHardBound; /// @notice The asset ratio bounds outside which swapping is still functional but, - /// the swap fees transition from a flat percentage fee to a linear function. + /// the swap fees transition from a constant fee to a linear function. Range public arSoftBound; //-------------------------------------------------------------------------- @@ -189,17 +193,17 @@ contract BillBroker is BillBrokerFees({ mintFeePerc: 0, burnFeePerc: 0, - perpToUSDSwapFeePercs: Range({ lower: ONE, upper: ONE }), - usdToPerpSwapFeePercs: Range({ lower: ONE, upper: ONE }), + perpToUSDSwapFeeFactors: Range({ lower: ONE, upper: ONE }), + usdToPerpSwapFeeFactors: Range({ lower: ONE, upper: ONE }), protocolSwapSharePerc: 0 }) ); updateARBounds( // Soft bound - Range({ lower: 0, upper: type(uint256).max }), + Range({ lower: 0, upper: MAX_ASSET_RATIO }), // Hard bound - Range({ lower: 0, upper: type(uint256).max }) + Range({ lower: 0, upper: MAX_ASSET_RATIO }) ); } @@ -227,13 +231,27 @@ contract BillBroker is if ( fees_.mintFeePerc > ONE || fees_.burnFeePerc > ONE || - fees_.perpToUSDSwapFeePercs.lower > fees_.perpToUSDSwapFeePercs.upper || - fees_.usdToPerpSwapFeePercs.lower > fees_.usdToPerpSwapFeePercs.upper || fees_.protocolSwapSharePerc > ONE ) { revert InvalidPerc(); } + if ( + fees_.perpToUSDSwapFeeFactors.lower > fees_.perpToUSDSwapFeeFactors.upper || + fees_.perpToUSDSwapFeeFactors.lower < ONE || + fees_.perpToUSDSwapFeeFactors.upper > MAX_FEE_FACTOR + ) { + revert InvalidPerc(); + } + + if ( + fees_.usdToPerpSwapFeeFactors.lower > fees_.usdToPerpSwapFeeFactors.upper || + fees_.usdToPerpSwapFeeFactors.lower < ONE || + fees_.usdToPerpSwapFeeFactors.upper > MAX_FEE_FACTOR + ) { + revert InvalidPerc(); + } + fees = fees_; } @@ -424,7 +442,7 @@ contract BillBroker is // compute perp amount out ReserveState memory s = reserveState(); uint256 protocolFeePerpAmt; - (perpAmtOut, , protocolFeePerpAmt) = computeUSDToPerpSwapAmt(usdAmtIn, s); + (perpAmtOut, protocolFeePerpAmt) = computeUSDToPerpSwapAmt(usdAmtIn, s); if (usdAmtIn <= 0 || perpAmtOut <= 0) { revert UnacceptableSwap(); } @@ -458,7 +476,7 @@ contract BillBroker is // Compute swap amount ReserveState memory s = reserveState(); uint256 protocolFeeUsdAmt; - (usdAmtOut, , protocolFeeUsdAmt) = computePerpToUSDSwapAmt(perpAmtIn, s); + (usdAmtOut, protocolFeeUsdAmt) = computePerpToUSDSwapAmt(perpAmtIn, s); if (perpAmtIn <= 0 || usdAmtOut <= 0) { revert UnacceptableSwap(); } @@ -507,7 +525,7 @@ contract BillBroker is function computePerpToUSDSwapAmt( uint256 perpAmtIn ) public returns (uint256 usdAmtOut) { - (usdAmtOut, , ) = computePerpToUSDSwapAmt(perpAmtIn, reserveState()); + (usdAmtOut, ) = computePerpToUSDSwapAmt(perpAmtIn, reserveState()); } /// @notice Computes the amount of perp tokens swapped out, @@ -517,7 +535,7 @@ contract BillBroker is function computeUSDToPerpSwapAmt( uint256 usdAmtIn ) public returns (uint256 perpAmtOut) { - (perpAmtOut, , ) = computeUSDToPerpSwapAmt(usdAmtIn, reserveState()); + (perpAmtOut, ) = computeUSDToPerpSwapAmt(usdAmtIn, reserveState()); } /// @return s The reserve usd and perp token balances and prices. @@ -633,22 +651,39 @@ contract BillBroker is function computeMintAmtWithUSD( uint256 usdAmtIn, ReserveState memory s - ) public view returns (uint256) { + ) public view returns (uint256 mintAmt) { if (usdAmtIn <= 0) { return 0; } // We compute equal value of perp tokens going out. + uint256 totalSupply_ = totalSupply(); uint256 valueIn = s.usdPrice.mulDiv(usdAmtIn, usdUnitAmt); uint256 totalReserveVal = (s.usdPrice.mulDiv(s.usdBalance, usdUnitAmt) + s.perpPrice.mulDiv(s.perpBalance, perpUnitAmt)); - return - (totalReserveVal > 0) - ? valueIn.mulDiv(totalSupply(), totalReserveVal).mulDiv( - ONE - fees.mintFeePerc, - ONE - ) - : 0; + if (totalReserveVal == 0 || totalSupply_ == 0) { + return 0; + } + + // Compute mint amount. + mintAmt = valueIn.mulDiv(totalSupply_, totalReserveVal); + + // A single sided deposit is a combination of swap and mint. + // We first calculate the amount of usd swapped into perps and + // apply the swap fee only for that portion. + // The mint fees are waived, because single sided deposits + // push the pool back into balance. + uint256 percOfAmtInSwapped = ONE.mulDiv( + usdAmtIn - (s.usdBalance + usdAmtIn).mulDiv(mintAmt, totalSupply_), + usdAmtIn + ); + uint256 feeFactor = computeUSDToPerpSwapFeeFactor( + assetRatio(s), + assetRatio(_updatedReserveState(s, s.usdBalance + usdAmtIn, s.perpBalance)) + ); + mintAmt = + mintAmt.mulDiv(percOfAmtInSwapped, ONE).mulDiv(TWO - feeFactor, ONE) + + mintAmt.mulDiv(ONE - percOfAmtInSwapped, ONE); } /// @notice Computes the amount of LP tokens minted, @@ -659,22 +694,41 @@ contract BillBroker is function computeMintAmtWithPerp( uint256 perpAmtIn, ReserveState memory s - ) public view returns (uint256) { + ) public view returns (uint256 mintAmt) { if (perpAmtIn <= 0) { return 0; } // We compute equal value of perp tokens coming in. + uint256 totalSupply_ = totalSupply(); uint256 valueIn = s.perpPrice.mulDiv(perpAmtIn, perpUnitAmt); uint256 totalReserveVal = (s.usdPrice.mulDiv(s.usdBalance, usdUnitAmt) + s.perpPrice.mulDiv(s.perpBalance, perpUnitAmt)); - return - (totalReserveVal > 0) - ? valueIn.mulDiv(totalSupply(), totalReserveVal).mulDiv( - ONE - fees.mintFeePerc, - ONE - ) - : 0; + if (totalReserveVal == 0 || totalSupply_ == 0) { + return 0; + } + + // Compute mint amount. + mintAmt = (totalReserveVal > 0) + ? valueIn.mulDiv(totalSupply_, totalReserveVal) + : 0; + + // A single sided deposit is a combination of swap and mint. + // We first calculate the amount of perps swapped into usd and + // apply the swap fee only for that portion. + // The mint fees are waived, because single sided deposits + // push the pool back into balance. + uint256 percOfAmtInSwapped = ONE.mulDiv( + perpAmtIn - (s.perpBalance + perpAmtIn).mulDiv(mintAmt, totalSupply_), + perpAmtIn + ); + uint256 feeFactor = computePerpToUSDSwapFeeFactor( + assetRatio(s), + assetRatio(_updatedReserveState(s, s.usdBalance, s.perpBalance + perpAmtIn)) + ); + mintAmt = + mintAmt.mulDiv(percOfAmtInSwapped, ONE).mulDiv(TWO - feeFactor, ONE) + + mintAmt.mulDiv(ONE - percOfAmtInSwapped, ONE); } /// @notice Computes the amount of usd and perp tokens redeemed, @@ -706,24 +760,19 @@ contract BillBroker is /// @param s The current reserve state. /// @dev Quoted usd token amount out includes the fees withheld. /// @return usdAmtOut The amount of usd tokens swapped out. - /// @return lpFeeUsdAmt The amount of usd tokens charged as swap fees by LPs. /// @return protocolFeeUsdAmt The amount of usd tokens charged as protocol fees. function computePerpToUSDSwapAmt( uint256 perpAmtIn, ReserveState memory s - ) - public - view - returns (uint256 usdAmtOut, uint256 lpFeeUsdAmt, uint256 protocolFeeUsdAmt) - { + ) public view returns (uint256 usdAmtOut, uint256 protocolFeeUsdAmt) { // We compute equal value tokens to swap out. usdAmtOut = perpAmtIn.mulDiv(s.perpPrice, s.usdPrice).mulDiv( usdUnitAmt, perpUnitAmt ); - // We compute the total fee percentage, lp fees and protocol fees - uint256 totalFeePerc = computePerpToUSDSwapFeePerc( + // We compute the fee factor + uint256 feeFactor = computePerpToUSDSwapFeeFactor( assetRatio(s), assetRatio( _updatedReserveState( @@ -733,13 +782,12 @@ contract BillBroker is ) ) ); - if (totalFeePerc >= ONE) { - return (0, 0, 0); + if (feeFactor >= ONE) { + protocolFeeUsdAmt = usdAmtOut + .mulDiv(feeFactor - ONE, ONE, MathUpgradeable.Rounding.Up) + .mulDiv(fees.protocolSwapSharePerc, ONE, MathUpgradeable.Rounding.Up); } - uint256 totalFeeUsdAmt = usdAmtOut.mulDiv(totalFeePerc, ONE); - usdAmtOut -= totalFeeUsdAmt; - protocolFeeUsdAmt = totalFeeUsdAmt.mulDiv(fees.protocolSwapSharePerc, ONE); - lpFeeUsdAmt = totalFeeUsdAmt - protocolFeeUsdAmt; + usdAmtOut = usdAmtOut.mulDiv(TWO - feeFactor, ONE); } /// @notice Computes the amount of perp tokens swapped out, @@ -748,24 +796,19 @@ contract BillBroker is /// @param s The current reserve state. /// @dev Quoted perp token amount out includes the fees withheld. /// @return perpAmtOut The amount of perp tokens swapped out. - /// @return lpFeePerpAmt The amount of perp tokens charged as swap fees by LPs. /// @return protocolFeePerpAmt The amount of perp tokens charged as protocol fees. function computeUSDToPerpSwapAmt( uint256 usdAmtIn, ReserveState memory s - ) - public - view - returns (uint256 perpAmtOut, uint256 lpFeePerpAmt, uint256 protocolFeePerpAmt) - { + ) public view returns (uint256 perpAmtOut, uint256 protocolFeePerpAmt) { // We compute equal value tokens to swap out. perpAmtOut = usdAmtIn.mulDiv(s.usdPrice, s.perpPrice).mulDiv( perpUnitAmt, usdUnitAmt ); - // We compute the total fee percentage, lp fees and protocol fees - uint256 totalFeePerc = computeUSDToPerpSwapFeePerc( + // We compute the fee factor + uint256 feeFactor = computeUSDToPerpSwapFeeFactor( assetRatio(s), assetRatio( _updatedReserveState( @@ -775,122 +818,104 @@ contract BillBroker is ) ) ); - if (totalFeePerc >= ONE) { - return (0, 0, 0); + if (feeFactor >= ONE) { + protocolFeePerpAmt = perpAmtOut + .mulDiv(feeFactor - ONE, ONE, MathUpgradeable.Rounding.Up) + .mulDiv(fees.protocolSwapSharePerc, ONE, MathUpgradeable.Rounding.Up); } - uint256 totalFeePerpAmt = perpAmtOut.mulDiv(totalFeePerc, ONE); - perpAmtOut -= totalFeePerpAmt; - protocolFeePerpAmt = totalFeePerpAmt.mulDiv(fees.protocolSwapSharePerc, ONE); - lpFeePerpAmt = totalFeePerpAmt - protocolFeePerpAmt; + perpAmtOut = perpAmtOut.mulDiv(TWO - feeFactor, ONE); } - /// @notice Computes the swap fee percentage when swapping from perp to usd tokens. + /// @notice Computes the swap fee factor when swapping from perp to usd tokens. /// @dev Swapping from perp to usd tokens, leaves the system with more perp and fewer usd tokens /// thereby decreasing the system's `assetRatio`. Thus arPost < arPre. /// @param arPre The asset ratio of the system before swapping. /// @param arPost The asset ratio of the system after swapping. - /// @return The fee percentage. - function computePerpToUSDSwapFeePerc( + /// @return The fee factor. + function computePerpToUSDSwapFeeFactor( uint256 arPre, uint256 arPost ) public view returns (uint256) { - if (arPost > arPre) { - revert UnexpectedARDelta(); + // When the ar decreases below the lower bound swaps are halted. + // Fees are set to 100% or a fee factor of 2.0 + if (arPost < arHardBound.lower) { + return TWO; } - // When the ar decreases below the lower bound, - // swaps are effectively halted by setting fees to 100%. - if (arPost < arHardBound.lower) { - return ONE; - } - // When the ar is between the soft and hard bound, a linear function is applied. - // When the ar is above the soft bound, a flat percentage fee is applied. - // - // fee - // ^ - // | - // fh | \ | - // | \ | - // | \ | - // | \ | - // | \ | - // | \ | - // fl | \__________ - // | | - // | | - // | | - // +---------------------------> ar - // arHL arSL 1.0 - // - Range memory swapFeePercs = fees.perpToUSDSwapFeePercs; + Range memory feeP2U = fees.perpToUSDSwapFeeFactors; + // The maximum -ve fee, inferred from fees for the other swap direction. + uint256 feeU2PUpperInv = (TWO - fees.usdToPerpSwapFeeFactors.upper); return - _computeFeePerc( - Line({ - x1: arHardBound.lower, - y1: swapFeePercs.upper, - x2: arSoftBound.lower, - y2: swapFeePercs.lower - }), - Line({ x1: 0, y1: swapFeePercs.lower, x2: ONE, y2: swapFeePercs.lower }), - arPost, - arPre, - arSoftBound.lower - ); - } - - /// @notice Computes the swap fee percentage when swapping from usd to perp tokens. + LineHelpers + .computePiecewiseAvgY( + Line({ + x1: arHardBound.lower, + y1: feeP2U.upper, + x2: arSoftBound.lower, + y2: feeP2U.lower + }), + Line({ + x1: arSoftBound.lower, + y1: feeP2U.lower, + x2: arSoftBound.upper, + y2: feeP2U.lower + }), + Line({ + x1: arSoftBound.upper, + y1: feeP2U.lower, + x2: arHardBound.upper, + y2: feeU2PUpperInv + }), + arSoftBound, + Range({ lower: arPost, upper: arPre }) + ) + .clip(feeU2PUpperInv, feeP2U.upper); + } + + /// @notice Computes the swap fee factor when swapping from usd to perp tokens. /// @dev Swapping from usd to perp tokens, leaves the system with more usd and fewer perp tokens /// thereby increasing the system's `assetRatio`. Thus arPost > arPre. /// @param arPre The asset ratio of the system before swapping. /// @param arPost The asset ratio of the system after swapping. - /// @return The fee percentage. - function computeUSDToPerpSwapFeePerc( + /// @return The fee factor. + function computeUSDToPerpSwapFeeFactor( uint256 arPre, uint256 arPost ) public view returns (uint256) { - if (arPost < arPre) { - revert UnexpectedARDelta(); + // When the ar increases above the hard bound, swaps are halted. + // Fees are set to 100% or a fee factor of 2.0 + if (arPost > arHardBound.upper) { + return TWO; } - // When the ar increases above the hard bound, - // swaps are effectively halted by setting fees to 100%. - if (arPost > arHardBound.upper) { - return ONE; - } - - // When the ar is between the soft and hard bound, a linear function is applied. - // When the ar is below the soft bound, a flat percentage fee is applied. - // - // fee - // ^ - // | - // fh | | / - // | | / - // | | / - // | | / - // | | / - // | | / - // fl | __________/ - // | | - // | | - // | | - // +---------------------------> ar - // 1.0 arSU arHU - // - Range memory swapFeePercs = fees.usdToPerpSwapFeePercs; + Range memory feeU2P = fees.usdToPerpSwapFeeFactors; + // The maximum -ve fee, inferred from fees for the other swap direction. + uint256 feeP2UUpperInv = (TWO - fees.perpToUSDSwapFeeFactors.upper); return - _computeFeePerc( - Line({ x1: 0, y1: swapFeePercs.lower, x2: ONE, y2: swapFeePercs.lower }), - Line({ - x1: arSoftBound.upper, - y1: swapFeePercs.lower, - x2: arHardBound.upper, - y2: swapFeePercs.upper - }), - arPre, - arPost, - arSoftBound.upper - ); + LineHelpers + .computePiecewiseAvgY( + Line({ + x1: arHardBound.lower, + y1: feeP2UUpperInv, + x2: arSoftBound.lower, + y2: feeU2P.lower + }), + Line({ + x1: arSoftBound.lower, + y1: feeU2P.lower, + x2: arSoftBound.upper, + y2: feeU2P.lower + }), + Line({ + x1: arSoftBound.upper, + y1: feeU2P.lower, + x2: arHardBound.upper, + y2: feeU2P.upper + }), + arSoftBound, + Range({ lower: arPre, upper: arPost }) + ) + .clip(feeP2UUpperInv, feeU2P.upper); } /// @param s The system reserve state. @@ -904,7 +929,7 @@ contract BillBroker is s.perpBalance.mulDiv(s.perpPrice, perpUnitAmt) ) ) - : type(uint256).max; + : MAX_ASSET_RATIO; } /// @notice The address which holds any revenue extracted by protocol. @@ -930,23 +955,4 @@ contract BillBroker is perpPrice: s.perpPrice }); } - - /// @dev The function assumes the fee curve is defined as a pair-wise linear function which merge at the cutoff point. - /// The swap fee is computed as avg height of the fee curve between {arL,arU}. - function _computeFeePerc( - Line memory fn1, - Line memory fn2, - uint256 arL, - uint256 arU, - uint256 cutoff - ) private pure returns (uint256 feePerc) { - if (arU <= cutoff) { - feePerc = fn1.avgY(arL, arU).clip(0, ONE); - } else if (arL >= cutoff) { - feePerc = fn2.avgY(arL, arU).clip(0, ONE); - } else { - feePerc += fn1.avgY(arL, cutoff).clip(0, ONE).mulDiv(cutoff - arL, arU - arL); - feePerc += fn2.avgY(cutoff, arU).clip(0, ONE).mulDiv(arU - cutoff, arU - arL); - } - } } diff --git a/spot-vaults/contracts/_interfaces/errors/CommonErrors.sol b/spot-vaults/contracts/_interfaces/errors/CommonErrors.sol index c8d6b263..42e6d9b0 100644 --- a/spot-vaults/contracts/_interfaces/errors/CommonErrors.sol +++ b/spot-vaults/contracts/_interfaces/errors/CommonErrors.sol @@ -18,3 +18,9 @@ error UnacceptableSwap(); /// @notice Expected usable external price. error UnreliablePrice(); + +/// @notice Range upper is larger than lower +error InvalidRange(); + +/// @notice Expected range delta to be smaller. +error UnexpectedRangeDelta(); diff --git a/spot-vaults/contracts/_interfaces/types/BillBrokerTypes.sol b/spot-vaults/contracts/_interfaces/types/BillBrokerTypes.sol index a99b3c27..6b61fcb7 100644 --- a/spot-vaults/contracts/_interfaces/types/BillBrokerTypes.sol +++ b/spot-vaults/contracts/_interfaces/types/BillBrokerTypes.sol @@ -9,10 +9,11 @@ struct BillBrokerFees { uint256 mintFeePerc; /// @notice The percentage fee charged for burning BillBroker LP tokens. uint256 burnFeePerc; - /// @notice Range of fee percentages for swapping from perp tokens to USD. - Range perpToUSDSwapFeePercs; - /// @notice Range of fee percentages for swapping from USD to perp tokens. - Range usdToPerpSwapFeePercs; + /// @notice Range of fee factors for swapping from perp tokens to USD. + /// @dev Factor of 1.02 implies a +2% fees, and 0.98 implies a -2% fees. + Range perpToUSDSwapFeeFactors; + /// @notice Range of fee factors for swapping from USD to perp tokens. + Range usdToPerpSwapFeeFactors; /// @notice The percentage of the swap fees that goes to the protocol. uint256 protocolSwapSharePerc; } diff --git a/spot-vaults/contracts/_test/LineHelpersTester.sol b/spot-vaults/contracts/_test/LineHelpersTester.sol new file mode 100644 index 00000000..e3fbc9f6 --- /dev/null +++ b/spot-vaults/contracts/_test/LineHelpersTester.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +import { LineHelpers } from "../_utils/LineHelpers.sol"; +import { Line, Range } from "../_interfaces/types/CommonTypes.sol"; + +contract LineHelpersTester { + using LineHelpers for Line; + + function testComputeY(Line memory fn, uint256 x) public pure returns (int256) { + return fn.computeY(x); + } + + function testAvgY( + Line memory fn, + uint256 xL, + uint256 xU + ) public pure returns (int256) { + return fn.avgY(xL, xU); + } + + function testComputePiecewiseAvgY( + Line memory fn1, + Line memory fn2, + Line memory fn3, + Range memory xBreakPt, + Range memory xRange + ) public pure returns (int256) { + return LineHelpers.computePiecewiseAvgY(fn1, fn2, fn3, xBreakPt, xRange); + } +} diff --git a/spot-vaults/contracts/_utils/LineHelpers.sol b/spot-vaults/contracts/_utils/LineHelpers.sol index f0ce2346..3eb077ce 100644 --- a/spot-vaults/contracts/_utils/LineHelpers.sol +++ b/spot-vaults/contracts/_utils/LineHelpers.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.24; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { Line } from "../_interfaces/types/CommonTypes.sol"; +import { MathHelpers } from "./MathHelpers.sol"; +import { Line, Range } from "../_interfaces/types/CommonTypes.sol"; +import { InvalidRange, UnexpectedRangeDelta } from "../_interfaces/errors/CommonErrors.sol"; /** * @title LineHelpers @@ -11,7 +14,26 @@ import { Line } from "../_interfaces/types/CommonTypes.sol"; * */ library LineHelpers { + using Math for uint256; + using MathHelpers for uint256; using SafeCast for uint256; + using SafeCast for int256; + + /// @dev This function computes y for a given x on the line (fn). + function computeY(Line memory fn, uint256 x) internal pure returns (int256) { + // If the line has a zero slope, return any y. + if (fn.y1 == fn.y2) { + return fn.y1.toInt256(); + } + + // m = dlY/dlX + // c = y2 - m . x2 + // y = m . x + c + int256 dlY = fn.y2.toInt256() - fn.y1.toInt256(); + int256 dlX = fn.x2.toInt256() - fn.x1.toInt256(); + int256 c = fn.y2.toInt256() - ((fn.x2.toInt256() * dlY) / dlX); + return (((x.toInt256() * dlY) / dlX) + c); + } /// @dev We compute the average height of the line between {xL,xU}. function avgY(Line memory fn, uint256 xL, uint256 xU) internal pure returns (int256) { @@ -20,6 +42,7 @@ library LineHelpers { return fn.y2.toInt256(); } + // NOTE: There is some precision loss because we cast to int and back // m = dlY/dlX // c = y2 - m . x2 // Avg height => (yL + yU) / 2 @@ -30,19 +53,72 @@ library LineHelpers { return ((((xL + xU).toInt256() * dlY) / (2 * dlX)) + c); } - /// @dev This function computes y for a given x on the line (fn). - function computeY(Line memory fn, uint256 x) internal pure returns (int256) { - // If the line has a zero slope, return any y. - if (fn.y1 == fn.y2) { - return fn.y1.toInt256(); + /// @notice Computes a piecewise average value (yVal) over the domain xRange, + /// based on three linear segments (fn1, fn2, fn3) that switch at xBreakPt. + /// @dev The function splits the input range into up to three segments, then + /// calculates a weighted average in each segment using the corresponding + /// piecewise function. + /// @dev AI-GENERATED + /// @param fn1 Piecewise linear function used when x is below xBreakPt.lower. + /// @param fn2 Piecewise linear function used when x is between xBreakPt.lower and xBreakPt.upper. + /// @param fn3 Piecewise linear function used when x is above xBreakPt.upper. + /// @param xBreakPt Range denoting the lower and upper x thresholds. + /// @param xRange The actual x-range over which we want to compute an averaged value. + /// @return yVal The computed piecewise average. + function computePiecewiseAvgY( + Line memory fn1, + Line memory fn2, + Line memory fn3, + Range memory xBreakPt, + Range memory xRange + ) internal pure returns (int256) { + int256 xl = xRange.lower.toInt256(); + int256 xu = xRange.upper.toInt256(); + int256 bpl = xBreakPt.lower.toInt256(); + int256 bpu = xBreakPt.upper.toInt256(); + + // Validate range inputs (custom errors omitted here). + if (xl > xu) revert InvalidRange(); + if (xl <= bpl && xu > bpu) revert UnexpectedRangeDelta(); + + // --------------------------- + // CASE A: Entire xRange below xBreakPt.lower → use fn1 + if (xu <= bpl) { + return avgY(fn1, xRange.lower, xRange.upper); } - // m = dlY/dlX - // c = y2 - m . x2 - // y = m . x + c - int256 dlY = fn.y2.toInt256() - fn.y1.toInt256(); - int256 dlX = fn.x2.toInt256() - fn.x1.toInt256(); - int256 c = fn.y2.toInt256() - ((fn.x2.toInt256() * dlY) / dlX); - return (((x.toInt256() * dlY) / dlX) + c); + // CASE B: xRange straddles bpl but still <= bpu + // Blend fn1 and fn2 + if (xl <= bpl && xu <= bpu) { + // w1 = portion in fn1, w2 = portion in fn2 + int256 w1 = bpl - xl; + int256 w2 = xu - bpl; + // Weighted average across two sub-ranges + return + (avgY(fn1, xRange.lower, xBreakPt.lower) * + w1 + + avgY(fn2, xBreakPt.lower, xRange.upper) * + w2) / (w1 + w2); + } + + // CASE C: Fully within [bpl, bpu] → use fn2 + if (xl > bpl && xu <= bpu) { + return avgY(fn2, xRange.lower, xRange.upper); + } + + // CASE D: xRange straddles xBreakPt.upper → blend fn2 and fn3 + if (xl <= bpu && xu > bpu) { + int256 w1 = bpu - xl; + int256 w2 = xu - bpu; + return + (avgY(fn2, xRange.lower, xBreakPt.upper) * + w1 + + avgY(fn3, xBreakPt.upper, xRange.upper) * + w2) / (w1 + w2); + } + + // CASE E: Entire xRange above xBreakPt.upper → use fn3 + // (if none of the above conditions matched, we must be here) + return avgY(fn3, xRange.lower, xRange.upper); } } diff --git a/spot-vaults/contracts/_utils/MathHelpers.sol b/spot-vaults/contracts/_utils/MathHelpers.sol index 1631648d..4b7d3665 100644 --- a/spot-vaults/contracts/_utils/MathHelpers.sol +++ b/spot-vaults/contracts/_utils/MathHelpers.sol @@ -16,6 +16,11 @@ library MathHelpers { /// @dev Clips a given integer number between provided min and max unsigned integer. function clip(int256 n, uint256 min, uint256 max) internal pure returns (uint256) { - return Math.min(Math.max((n >= 0) ? n.toUint256() : 0, min), max); + return clip(n > 0 ? n.toUint256() : 0, min, max); + } + + /// @dev Clips a given unsigned integer between provided min and max unsigned integer. + function clip(uint256 n, uint256 min, uint256 max) internal pure returns (uint256) { + return Math.min(Math.max(n, min), max); } } diff --git a/spot-vaults/package.json b/spot-vaults/package.json index 1368665c..12986136 100644 --- a/spot-vaults/package.json +++ b/spot-vaults/package.json @@ -62,7 +62,7 @@ "ethers": "^6.6.0", "ethers-v5": "npm:ethers@^5.7.0", "ganache-cli": "latest", - "hardhat": "^2.22.10", + "hardhat": "^2.22.18", "hardhat-gas-reporter": "latest", "lodash": "^4.17.21", "prettier": "^2.7.1", diff --git a/spot-vaults/test/BillBroker.ts b/spot-vaults/test/BillBroker.ts index a44ac000..71c2afd0 100644 --- a/spot-vaults/test/BillBroker.ts +++ b/spot-vaults/test/BillBroker.ts @@ -42,20 +42,20 @@ describe("BillBroker", function () { expect(await billBroker.keeper()).to.eq(await deployer.getAddress()); const arHardBound = await billBroker.arHardBound(); - expect(arHardBound.upper).to.eq(ethers.MaxUint256); - expect(arHardBound.lower).to.eq(0n); + expect(arHardBound.upper).to.eq(ethers.MaxInt256); + expect(arHardBound.lower).to.eq(0); const arSoftBound = await billBroker.arSoftBound(); - expect(arSoftBound.upper).to.eq(ethers.MaxUint256); - expect(arSoftBound.lower).to.eq(0n); + expect(arSoftBound.upper).to.eq(ethers.MaxInt256); + expect(arSoftBound.lower).to.eq(0); const fees = await billBroker.fees(); expect(fees.mintFeePerc).to.eq(0); expect(fees.burnFeePerc).to.eq(0); - expect(fees.perpToUSDSwapFeePercs.lower).to.eq(percFP("1")); - expect(fees.perpToUSDSwapFeePercs.upper).to.eq(percFP("1")); - expect(fees.usdToPerpSwapFeePercs.lower).to.eq(percFP("1")); - expect(fees.usdToPerpSwapFeePercs.upper).to.eq(percFP("1")); + expect(fees.perpToUSDSwapFeeFactors.lower).to.eq(percFP("1")); + expect(fees.perpToUSDSwapFeeFactors.upper).to.eq(percFP("1")); + expect(fees.usdToPerpSwapFeeFactors.lower).to.eq(percFP("1")); + expect(fees.usdToPerpSwapFeeFactors.upper).to.eq(percFP("1")); expect(fees.protocolSwapSharePerc).to.eq(0); expect(await billBroker.usdBalance()).to.eq(0n); @@ -122,13 +122,13 @@ describe("BillBroker", function () { fees = { mintFeePerc: percFP("0.005"), burnFeePerc: percFP("0.025"), - perpToUSDSwapFeePercs: { - lower: percFP("0.01"), - upper: percFP("0.1"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.01"), + upper: percFP("1.1"), }, - usdToPerpSwapFeePercs: { - lower: percFP("0.02"), - upper: percFP("0.2"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.02"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.05"), }; @@ -166,8 +166,8 @@ describe("BillBroker", function () { }); it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.perpToUSDSwapFeePercs.lower = percFP("0.2"); - fees.perpToUSDSwapFeePercs.upper = percFP("0.1"); + fees.perpToUSDSwapFeeFactors.lower = percFP("0.2"); + fees.perpToUSDSwapFeeFactors.upper = percFP("0.1"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -175,8 +175,8 @@ describe("BillBroker", function () { }); it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.usdToPerpSwapFeePercs.lower = percFP("0.2"); - fees.usdToPerpSwapFeePercs.upper = percFP("0.1"); + fees.usdToPerpSwapFeeFactors.lower = percFP("0.2"); + fees.usdToPerpSwapFeeFactors.upper = percFP("0.1"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -199,10 +199,10 @@ describe("BillBroker", function () { const f = await billBroker.fees(); expect(f.mintFeePerc).to.eq(fees.mintFeePerc); expect(f.burnFeePerc).to.eq(fees.burnFeePerc); - expect(f.perpToUSDSwapFeePercs.lower).to.eq(fees.perpToUSDSwapFeePercs.lower); - expect(f.perpToUSDSwapFeePercs.upper).to.eq(fees.perpToUSDSwapFeePercs.upper); - expect(f.usdToPerpSwapFeePercs.lower).to.eq(fees.usdToPerpSwapFeePercs.lower); - expect(f.usdToPerpSwapFeePercs.upper).to.eq(fees.usdToPerpSwapFeePercs.upper); + expect(f.perpToUSDSwapFeeFactors.lower).to.eq(fees.perpToUSDSwapFeeFactors.lower); + expect(f.perpToUSDSwapFeeFactors.upper).to.eq(fees.perpToUSDSwapFeeFactors.upper); + expect(f.usdToPerpSwapFeeFactors.lower).to.eq(fees.usdToPerpSwapFeeFactors.lower); + expect(f.usdToPerpSwapFeeFactors.upper).to.eq(fees.usdToPerpSwapFeeFactors.upper); expect(f.protocolSwapSharePerc).to.eq(fees.protocolSwapSharePerc); }); }); @@ -414,161 +414,301 @@ describe("BillBroker", function () { }); }); - describe("#computeUSDToPerpSwapFeePerc", function () { - it("should compute the right fee perc", async function () { + describe("#computeUSDToPerpSwapFeeFactor", function () { + it("should compute the right factor perc", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( [percFP("0.75"), percFP("1.25")], - [percFP("0.5"), percFP("1.5")], + [percFP("0.25"), percFP("4")], ); await billBroker.updateFees({ mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.16"), }, - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("1.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.08"), }, protocolSwapSharePerc: 0n, }); await expect( - billBroker.computeUSDToPerpSwapFeePerc(percFP("1.5"), percFP("0.5")), - ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); + billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.5"), percFP("0.5")), + ).to.be.revertedWithCustomError(billBroker, "InvalidRange"); + await expect( + billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.25"), percFP("1.249")), + ).to.be.revertedWithCustomError(billBroker, "InvalidRange"); + + await expect( + billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.75"), percFP("1.26")), + ).to.be.revertedWithCustomError(billBroker, "UnexpectedRangeDelta"); await expect( - billBroker.computeUSDToPerpSwapFeePerc(percFP("1.25"), percFP("1.249")), - ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); + billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.5"), percFP("1.5")), + ).to.be.revertedWithCustomError(billBroker, "UnexpectedRangeDelta"); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("0.25"), percFP("1.2")), - ).to.eq(percFP("0.05")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.1"), percFP("0.26")), + ).to.eq(percFP("0.84")); + expect( + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.3"), percFP("0.5")), + ).to.eq(percFP("0.8955")); + expect( + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.25"), percFP("1")), + ).to.eq(percFP("0.963333333333333333")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("0.25"), percFP("1.25")), - ).to.eq(percFP("0.05")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.25"), percFP("1.24")), + ).to.eq(percFP("0.978282828282828282")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.2"), percFP("1.3")), - ).to.eq(percFP("0.1225")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("0.76"), percFP("1.24")), + ).to.eq(percFP("1.025")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("1.45")), - ).to.eq(percFP("0.775")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.2"), percFP("1.3")), + ).to.eq(percFP("1.02525")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("1.5")), - ).to.eq(percFP("0.92")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.3"), percFP("1.45")), + ).to.eq(percFP("1.0275")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("0.5"), percFP("1.5")), - ).to.eq(percFP("0.23125")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.3"), percFP("1.5")), + ).to.eq(percFP("1.028")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("1.501")), - ).to.eq(percFP("1")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.3"), percFP("1.501")), + ).to.eq(percFP("1.02801")); expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("2")), - ).to.eq(percFP("1")); + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.3"), percFP("2")), + ).to.eq(percFP("1.033")); + expect( + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.5"), percFP("4")), + ).to.eq(percFP("1.055")); + expect( + await billBroker.computeUSDToPerpSwapFeeFactor(percFP("1.3"), percFP("4.01")), + ).to.eq(percFP("2")); }); - it("should compute the right fee perc when outside bounds", async function () { - const { billBroker } = await loadFixture(setupContracts); - await billBroker.updateARBounds( - [percFP("0.75"), percFP("1.25")], - [percFP("0"), percFP("10")], - ); + describe("Extended coverage for break-point conditions & edge cases", function () { + let billBroker; - await billBroker.updateFees({ - mintFeePerc: 0n, - burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, - }, - usdToPerpSwapFeePercs: { - lower: percFP("1.01"), - upper: percFP("2"), - }, - protocolSwapSharePerc: 0n, + beforeEach(async () => { + const fixtures = await loadFixture(setupContracts); + billBroker = fixtures.billBroker; + + await billBroker.updateARBounds( + [percFP("0.75"), percFP("1.25")], + [percFP("0.25"), percFP("4")], + ); + + await billBroker.updateFees({ + mintFeePerc: 0n, + burnFeePerc: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.0769"), + }, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1693"), + }, + protocolSwapSharePerc: 0n, + }); + }); + + it("Case A: Entire range below arSoftBound.lower => uses fn1 entirely", async () => { + const result = await billBroker.computeUSDToPerpSwapFeeFactor( + percFP("0.50"), + percFP("0.55"), + ); + expect(result).to.eq(percFP("0.979145")); }); - expect( - await billBroker.computeUSDToPerpSwapFeePerc(percFP("1"), percFP("1.25")), - ).to.eq(percFP("1")); + it("Case B: Range straddles arSoftBound.lower => partial weighting fn1/fn2", async () => { + const result = await billBroker.computeUSDToPerpSwapFeeFactor( + percFP("0.70"), + percFP("0.80"), + ); + expect(result).to.eq(percFP("1.0224525")); + }); + + it("Case C: Range fully within [arSoftBound.lower..arSoftBound.upper] => uses fn2 entirely", async () => { + const result = await billBroker.computeUSDToPerpSwapFeeFactor( + percFP("1.0"), + percFP("1.2"), + ); + expect(result).to.eq(percFP("1.025")); + }); + + it("Case D: Range straddles arSoftBound.upper => partial weighting fn2/fn3", async () => { + const result = await billBroker.computeUSDToPerpSwapFeeFactor( + percFP("1.20"), + percFP("1.30"), + ); + expect(result).to.eq(percFP("1.025655909090909091")); + }); + + it("Case E: Entire range above arSoftBound.upper => uses fn3 entirely", async () => { + const result = await billBroker.computeUSDToPerpSwapFeeFactor( + percFP("1.5"), + percFP("3"), + ); + expect(result).to.eq(percFP("1.077472727272727273")); + }); + + it("Zero-length range at boundary (e.g., arPre=arPost=arSoftBound.lower) => picks boundary side", async () => { + const result = await billBroker.computeUSDToPerpSwapFeeFactor( + percFP("0.75"), + percFP("0.75"), + ); + expect(result).to.eq(percFP("1.025")); + }); }); }); - describe("#computePerpToUSDSwapFeePerc", function () { - it("should compute the right fee perc", async function () { + describe("#computePerpToUSDSwapFeeFactor", function () { + it("should compute the right fee factor", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( [percFP("0.75"), percFP("1.25")], - [percFP("0.5"), percFP("1.5")], + [percFP("0.25"), percFP("4")], ); await billBroker.updateFees({ mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.15"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.2"), }, protocolSwapSharePerc: 0n, }); await expect( - billBroker.computePerpToUSDSwapFeePerc(percFP("0.5"), percFP("1.5")), - ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); + billBroker.computePerpToUSDSwapFeeFactor(percFP("1.25"), percFP("1.251")), + ).to.be.revertedWithCustomError(billBroker, "InvalidRange"); await expect( - billBroker.computePerpToUSDSwapFeePerc(percFP("1.25"), percFP("1.251")), - ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); + billBroker.computePerpToUSDSwapFeeFactor(percFP("1.5"), percFP("0.5")), + ).to.be.revertedWithCustomError(billBroker, "UnexpectedRangeDelta"); expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("2"), percFP("0.8")), - ).to.eq(percFP("0.1")); + await billBroker.computePerpToUSDSwapFeeFactor(percFP("4"), percFP("3")), + ).to.eq(percFP("0.854545454545454545")); + expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("1.45"), percFP("0.8")), - ).to.eq(percFP("0.1")); + await billBroker.computePerpToUSDSwapFeeFactor(percFP("4"), percFP("1.25")), + ).to.eq(percFP("0.95")); + expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("0.8"), percFP("0.7")), - ).to.eq(percFP("0.12")); + await billBroker.computePerpToUSDSwapFeeFactor(percFP("3"), percFP("2")), + ).to.eq(percFP("0.963636363636363636")); + expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("0.8"), percFP("0.5")), - ).to.eq(percFP("0.266666666666666666")); + await billBroker.computePerpToUSDSwapFeeFactor(percFP("2"), percFP("1.5")), + ).to.eq(percFP("1.045454545454545454")); + + expect( + await billBroker.computePerpToUSDSwapFeeFactor(percFP("2"), percFP("0.8")), + ).to.eq(percFP("1.074431818181818181")); + expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("1.5"), percFP("0.5")), - ).to.eq(percFP("0.15")); + await billBroker.computePerpToUSDSwapFeeFactor(percFP("1.45"), percFP("0.8")), + ).to.eq(percFP("1.096643356643356643")); + + expect( + await billBroker.computePerpToUSDSwapFeeFactor(percFP("1.25"), percFP("0.9")), + ).to.eq(percFP("1.1")); + expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("1.0"), percFP("0.49")), - ).to.eq(percFP("1")); + await billBroker.computePerpToUSDSwapFeeFactor(percFP("0.8"), percFP("0.7")), + ).to.eq(percFP("1.10125")); + + expect( + await billBroker.computePerpToUSDSwapFeeFactor(percFP("0.8"), percFP("0.5")), + ).to.eq(percFP("1.110416666666666666")); + + expect( + await billBroker.computePerpToUSDSwapFeeFactor(percFP("1.0"), percFP("0.49")), + ).to.eq(percFP("1.106627450980392156")); }); - it("should compute the right fee perc when outside bounds", async function () { - const { billBroker } = await loadFixture(setupContracts); - await billBroker.updateARBounds( - [percFP("0.75"), percFP("1.25")], - [percFP("0"), percFP("10")], - ); + describe("Extended coverage for break-point conditions & edge cases", function () { + let billBroker; - await billBroker.updateFees({ - mintFeePerc: 0n, - burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: percFP("1.01"), - upper: percFP("2"), - }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, - }, - protocolSwapSharePerc: 0n, + beforeEach(async () => { + const fixtures = await loadFixture(setupContracts); + billBroker = fixtures.billBroker; + + await billBroker.updateARBounds( + [percFP("0.75"), percFP("1.25")], + [percFP("0.25"), percFP("4")], + ); + + await billBroker.updateFees({ + mintFeePerc: 0n, + burnFeePerc: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.0769"), + }, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1693"), + }, + protocolSwapSharePerc: 0n, + }); + }); + + it("Case A: Entirely below arSoftBound.lower => uses fn1 for perp->USD swap", async () => { + const result = await billBroker.computePerpToUSDSwapFeeFactor( + percFP("0.60"), + percFP("0.50"), + ); + expect(result).to.eq(percFP("1.04576")); }); - expect( - await billBroker.computePerpToUSDSwapFeePerc(percFP("1.25"), percFP("1.11")), - ).to.eq(percFP("1")); + it("Case B: Straddles arSoftBound.lower => partial weighting", async () => { + const result = await billBroker.computePerpToUSDSwapFeeFactor( + percFP("0.80"), + percFP("0.70"), + ); + expect(result).to.eq(percFP("1.0262975")); + }); + + it("Case C: Fully within [0.75..1.25] => uses middle fn2", async () => { + const result = await billBroker.computePerpToUSDSwapFeeFactor( + percFP("1.20"), + percFP("1.10"), + ); + expect(result).to.eq(percFP("1.025")); + }); + + it("Case D: Straddles arSoftBound.upper => partial weighting", async () => { + const result = await billBroker.computePerpToUSDSwapFeeFactor( + percFP("1.30"), + percFP("1.20"), + ); + expect(result).to.eq(percFP("1.024116818181818182")); + }); + + it("Case E: Entirely above arSoftBound.upper => uses fn3", async () => { + const result = await billBroker.computePerpToUSDSwapFeeFactor( + percFP("2.50"), + percFP("2.0"), + ); + expect(result).to.eq(percFP("0.954345454545454546")); + }); + + it("Zero-length range exactly at boundary => picks boundary side", async () => { + const result = await billBroker.computePerpToUSDSwapFeeFactor( + percFP("1.25"), + percFP("1.25"), + ); + expect(result).to.eq(percFP("1.025")); + }); }); }); }); diff --git a/spot-vaults/test/BillBroker_deposit_redeem.ts b/spot-vaults/test/BillBroker_deposit_redeem.ts index 881f1c55..8832fc8d 100644 --- a/spot-vaults/test/BillBroker_deposit_redeem.ts +++ b/spot-vaults/test/BillBroker_deposit_redeem.ts @@ -31,13 +31,13 @@ describe("BillBroker", function () { await billBroker.updateFees({ mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, protocolSwapSharePerc: 0n, }); @@ -147,13 +147,13 @@ describe("BillBroker", function () { await billBroker.updateFees({ mintFeePerc: percFP("0.1"), burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, protocolSwapSharePerc: 0n, }); @@ -180,7 +180,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxInt256); const r = await billBroker.computeMintAmt.staticCall(usdFP("100"), 0n); expect(r[0]).to.eq(lpAmtFP("93.478260869565217391304347")); @@ -234,45 +234,49 @@ describe("BillBroker", function () { it("should return the mint amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); await usd.approve(billBroker.target, usdFP("115")); - await perp.approve(billBroker.target, perpFP("200")); + await perp.approve(billBroker.target, perpFP("2000")); await billBroker.deposit( usdFP("115"), - perpFP("200"), + perpFP("2000"), usdFP("115"), - perpFP("200"), + perpFP("2000"), ); expect(await billBroker.computeMintAmtWithUSD.staticCall(usdFP("11.5"))).to.eq( - lpAmtFP("10.5"), + lpAmtFP("10.071428571428571428571427"), ); }); }); - describe("when fee > 0", function () { + describe("when swapFee > 0", function () { it("should return the mint amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); + await billBroker.updateARBounds( + [percFP("0.9"), percFP("1.1")], + [percFP("0.25"), percFP("4")], + ); await usd.approve(billBroker.target, usdFP("115")); - await perp.approve(billBroker.target, perpFP("200")); + await perp.approve(billBroker.target, perpFP("2000")); await billBroker.deposit( usdFP("115"), - perpFP("200"), + perpFP("2000"), usdFP("115"), - perpFP("200"), + perpFP("2000"), ); await billBroker.updateFees({ - mintFeePerc: percFP("0.1"), + mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1.2"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.15"), }, protocolSwapSharePerc: 0n, }); expect(await billBroker.computeMintAmtWithUSD.staticCall(usdFP("11.5"))).to.eq( - lpAmtFP("9.45"), + lpAmtFP("11.980204248447204967542855"), ); }); }); @@ -321,46 +325,50 @@ describe("BillBroker", function () { describe("when fee = 0", function () { it("should return the mint amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await usd.approve(billBroker.target, usdFP("200")); + await usd.approve(billBroker.target, usdFP("2000")); await perp.approve(billBroker.target, perpFP("100")); await billBroker.deposit( - usdFP("200"), + usdFP("2000"), perpFP("100"), - usdFP("200"), + usdFP("2000"), perpFP("100"), ); expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("10.5"))).to.eq( - lpAmtFP("11.5"), + lpAmtFP("11.989361702127659574468084"), ); }); }); - describe("when fee > 0", function () { + describe("when swapFee > 0", function () { it("should return the mint amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await usd.approve(billBroker.target, usdFP("200")); + await billBroker.updateARBounds( + [percFP("0.9"), percFP("1.1")], + [percFP("0.25"), percFP("4")], + ); + await usd.approve(billBroker.target, usdFP("2000")); await perp.approve(billBroker.target, perpFP("100")); await billBroker.deposit( - usdFP("200"), + usdFP("2000"), perpFP("100"), - usdFP("200"), + usdFP("2000"), perpFP("100"), ); await billBroker.updateFees({ - mintFeePerc: percFP("0.1"), + mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: 0n, }); expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("10.5"))).to.eq( - lpAmtFP("10.35"), + lpAmtFP("14.243163296689361701442552"), ); }); }); @@ -381,7 +389,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxInt256); expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("100"))).to.eq( lpAmtFP("107.5"), ); @@ -565,15 +573,15 @@ describe("BillBroker", function () { it("should withhold fees and mint lp tokens", async function () { const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); await billBroker.updateFees({ - mintFeePerc: percFP("0.1"), + mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, protocolSwapSharePerc: 0n, }); @@ -581,7 +589,7 @@ describe("BillBroker", function () { await perp.approve(billBroker.target, perpFP("100")); await expect(() => billBroker.deposit(usdFP("115"), perpFP("100"), usdFP("115"), perpFP("100")), - ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("193.49")); + ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("214.99")); }); }); @@ -601,7 +609,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxInt256); await usd.approve(billBroker.target, usdFP("115")); await expect(() => @@ -670,7 +678,7 @@ describe("BillBroker", function () { ); await usd.approve(billBroker.target, usdFP("100")); expect( - await billBroker.depositUSD.staticCall(usdFP("100"), ethers.MaxUint256), + await billBroker.depositUSD.staticCall(usdFP("100"), ethers.MaxInt256), ).to.eq(0n); }); }); @@ -688,7 +696,7 @@ describe("BillBroker", function () { ); await usd.approve(billBroker.target, usdFP("100")); expect( - await billBroker.depositUSD.staticCall(usdFP("100"), ethers.MaxUint256), + await billBroker.depositUSD.staticCall(usdFP("100"), ethers.MaxInt256), ).to.eq(0n); }); }); @@ -706,7 +714,7 @@ describe("BillBroker", function () { ); await usd.approve(billBroker.target, usdFP("1")); expect( - await billBroker.depositUSD.staticCall(usdFP("1"), ethers.MaxUint256), + await billBroker.depositUSD.staticCall(usdFP("1"), ethers.MaxInt256), ).to.eq(0n); }); }); @@ -724,7 +732,7 @@ describe("BillBroker", function () { ); await usd.approve(billBroker.target, usdFP("115")); expect( - await billBroker.depositUSD.staticCall(usdFP("115"), ethers.MaxUint256), + await billBroker.depositUSD.staticCall(usdFP("115"), ethers.MaxInt256), ).to.eq(lpAmtFP("105")); }); }); @@ -783,10 +791,10 @@ describe("BillBroker", function () { ).to.changeTokenBalance( billBroker, deployer, - lpAmtFP("9.130434782608695652173913"), + lpAmtFP("9.130434782608695652173912"), ); expect(await billBroker.totalSupply()).to.eq( - lpAmtFP("324.130434782608695652173913"), + lpAmtFP("324.130434782608695652173912"), ); }); @@ -806,7 +814,7 @@ describe("BillBroker", function () { .to.emit(billBroker, "DepositUSD") .withArgs(usdFP("10"), r); expect(await billBroker.totalSupply()).to.eq( - lpAmtFP("324.130434782608695652173913"), + lpAmtFP("324.130434782608695652173912"), ); }); @@ -823,7 +831,7 @@ describe("BillBroker", function () { await usd.approve(billBroker.target, usdFP("10")); const r = await billBroker.depositUSD.staticCall(usdFP("10"), percFP("1")); - expect(r).to.eq(lpAmtFP("9.130434782608695652173913")); + expect(r).to.eq(lpAmtFP("9.130434782608695652173912")); }); }); @@ -831,15 +839,15 @@ describe("BillBroker", function () { it("should withhold fees and mint lp tokens", async function () { const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); await billBroker.updateFees({ - mintFeePerc: percFP("0.1"), + mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.2"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1"), }, protocolSwapSharePerc: 0n, }); @@ -859,8 +867,56 @@ describe("BillBroker", function () { ).to.changeTokenBalance( billBroker, deployer, - lpAmtFP("7.395652173913043478260868"), + lpAmtFP("8.984877117391304347826085"), + ); + + const r = await billBroker.computeRedemptionAmts.staticCall( + lpAmtFP("8.984877117391304347826085"), + ); + expect(r[0]).to.eq(usdFP("3.466549")); + expect(r[1]).to.eq(perpFP("5.546479327")); + }); + + it("should be roughly equivalent to swap+deposit", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await billBroker.updateFees({ + mintFeePerc: 0n, + burnFeePerc: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.2"), + }, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1"), + }, + protocolSwapSharePerc: 0n, + }); + + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + + await usd.approve(billBroker.target, usdFP("10")); + await perp.approve(billBroker.target, perpFP("10")); + await billBroker.swapUSDForPerps(usdFP("6.535"), 0n); + await expect(() => + billBroker.deposit(usdFP("3.465"), percFP("10"), 0n, 0n), + ).to.changeTokenBalance( + billBroker, + deployer, + lpAmtFP("8.980746287077796519521125"), + ); + const r = await billBroker.computeRedemptionAmts.staticCall( + lpAmtFP("8.980746287077796519521125"), ); + expect(r[0]).to.eq(usdFP("3.464999")); + expect(r[1]).to.eq(perpFP("5.544098565")); }); }); }); @@ -938,7 +994,7 @@ describe("BillBroker", function () { await billBroker.deposit(usdFP("115"), perpFP("90"), usdFP("115"), perpFP("90")); await perp.approve(billBroker.target, perpFP("10")); expect(await billBroker.depositPerp.staticCall(perpFP("10"), 0n)).to.eq( - lpAmtFP("10.789473684210526315789473"), + lpAmtFP("10.789473684210526315789472"), ); }); }); @@ -1047,22 +1103,69 @@ describe("BillBroker", function () { ); await billBroker.updateFees({ - mintFeePerc: percFP("0.1"), + mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.2"), }, protocolSwapSharePerc: 0n, }); await perp.approve(billBroker.target, perpFP("10")); await expect(() => billBroker.depositPerp(perpFP("10"), percFP("1")), - ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("9.9")); + ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("10.825833333315")); + + const r = await billBroker.computeRedemptionAmts.staticCall( + lpAmtFP("10.825833333315"), + ); + expect(r[0]).to.eq(usdFP("7.305613")); + expect(r[1]).to.eq(perpFP("3.493988865")); + }); + + it("should be roughly equivalent to swap+deposit", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await billBroker.updateFees({ + mintFeePerc: 0n, + burnFeePerc: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1"), + }, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.2"), + }, + protocolSwapSharePerc: 0n, + }); + await perp.approve(billBroker.target, perpFP("15")); + await usd.approve(billBroker.target, usdFP("15")); + await billBroker.swapPerpsForUSD(perpFP("6"), 0n); + await expect(() => + billBroker.deposit(usdFP("7.30"), percFP("5"), 0n, 0n), + ).to.changeTokenBalance( + billBroker, + deployer, + lpAmtFP("10.789506096809951964527651"), + ); + const r = await billBroker.computeRedemptionAmts.staticCall( + lpAmtFP("10.789506096809951964527651"), + ); + expect(r[0]).to.eq(usdFP("7.299999")); + expect(r[1]).to.eq(perpFP("3.465720140")); }); }); }); @@ -1125,13 +1228,13 @@ describe("BillBroker", function () { await billBroker.updateFees({ mintFeePerc: 0n, burnFeePerc: percFP("0.1"), - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, protocolSwapSharePerc: 0n, }); @@ -1165,7 +1268,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxInt256); const r = await billBroker.computeRedemptionAmts.staticCall(lpAmtFP("100")); expect(r[0]).to.eq(usdFP("106.976744")); @@ -1405,7 +1508,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxInt256); const perpBal = await perp.balanceOf(await deployer.getAddress()); await expect(() => billBroker.redeem(lpAmtFP("100"))).to.changeTokenBalance( diff --git a/spot-vaults/test/BillBroker_swap.ts b/spot-vaults/test/BillBroker_swap.ts index 2d44a158..91224688 100644 --- a/spot-vaults/test/BillBroker_swap.ts +++ b/spot-vaults/test/BillBroker_swap.ts @@ -10,11 +10,11 @@ async function updateFees(billBroker: Contract, fees: any) { ...{ mintFeePerc: currentFees[0], burnFeePerc: currentFees[1], - perpToUSDSwapFeePercs: { + perpToUSDSwapFeeFactors: { lower: currentFees[2][0], upper: currentFees[2][1], }, - usdToPerpSwapFeePercs: { + usdToPerpSwapFeeFactors: { lower: currentFees[3][0], upper: currentFees[3][1], }, @@ -28,28 +28,26 @@ async function checkUSDToPerpSwapAmt( billBroker: Contract, usdAmtIn: BigInt, reserveState: any, - amoutsOut: any, + returnVals: any, ) { const r = await billBroker[ "computeUSDToPerpSwapAmt(uint256,(uint256,uint256,uint256,uint256))" ](usdAmtIn, reserveState); - expect(r[0]).to.eq(amoutsOut[0]); - expect(r[1]).to.eq(amoutsOut[1]); - expect(r[2]).to.eq(amoutsOut[2]); + expect(r[0]).to.eq(returnVals[0]); + expect(r[1]).to.eq(returnVals[1]); } async function checkPerpToUSDSwapAmt( billBroker: Contract, perpAmtIn: BigInt, reserveState: any, - amoutsOut: any, + returnVals: any, ) { const r = await billBroker[ "computePerpToUSDSwapAmt(uint256,(uint256,uint256,uint256,uint256))" ](perpAmtIn, reserveState); - expect(r[0]).to.eq(amoutsOut[0]); - expect(r[1]).to.eq(amoutsOut[1]); - expect(r[2]).to.eq(amoutsOut[2]); + expect(r[0]).to.eq(returnVals[0]); + expect(r[1]).to.eq(returnVals[1]); } async function reserveState(billBroker: Contract) { @@ -94,13 +92,13 @@ describe("BillBroker", function () { await updateFees(billBroker, { mintFeePerc: 0n, burnFeePerc: 0n, - perpToUSDSwapFeePercs: { - lower: 0n, - upper: 0n, + perpToUSDSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, - usdToPerpSwapFeePercs: { - lower: 0n, - upper: 0n, + usdToPerpSwapFeeFactors: { + lower: percFP("1"), + upper: percFP("1"), }, protocolSwapSharePerc: 0n, }); @@ -132,7 +130,7 @@ describe("BillBroker", function () { billBroker, usdFP("115"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], - [perpFP("100"), 0n, 0n], + [perpFP("100"), 0n], ); }); @@ -142,7 +140,7 @@ describe("BillBroker", function () { billBroker, usdFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [perpFP("100"), 0n, 0n], + [perpFP("100"), 0n], ); }); @@ -152,7 +150,7 @@ describe("BillBroker", function () { billBroker, usdFP("11111"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [perpFP("11111"), 0n, 0n], + [perpFP("11111"), 0n], ); }); @@ -163,7 +161,7 @@ describe("BillBroker", function () { usdFP("11112"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [0n, 0n, 0n], + [0n, 0n], ); }); @@ -173,7 +171,7 @@ describe("BillBroker", function () { billBroker, usdFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], - [perpFP("111.111111"), 0n, 0n], + [perpFP("111.111111"), 0n], ); }); @@ -181,81 +179,104 @@ describe("BillBroker", function () { it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("115"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], - [perpFP("95"), perpFP("5"), 0n], + [perpFP("95"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [perpFP("95"), perpFP("5"), 0n], + [perpFP("95"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], - [perpFP("101.460470381"), perpFP("9.650640619"), 0n], + [perpFP("104.190527093"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("10000"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [perpFP("8491.666666667"), perpFP("1508.333333333"), 0n], + [perpFP("9163.888888888"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("20000"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + [0n, 0n], + ); + }); - [0n, 0n, 0n], + it("should return the perp amount and fees", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + perpToUSDSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.1"), + }, + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), + }, + }); + await billBroker.updateARBounds( + [percFP("0.25"), percFP("4")], + [percFP("0.01"), percFP("100")], + ); + await checkUSDToPerpSwapAmt( + billBroker, + usdFP("10000"), + [usdFP("1000"), perpFP("100000"), priceFP("1"), priceFP("1")], + [perpFP("10649.305555555"), 0n], ); }); }); @@ -264,9 +285,9 @@ describe("BillBroker", function () { it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -274,7 +295,7 @@ describe("BillBroker", function () { billBroker, usdFP("115"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], - [perpFP("95"), perpFP("4.5"), perpFP("0.5")], + [perpFP("95"), perpFP("0.5")], ); }); }); @@ -283,7 +304,7 @@ describe("BillBroker", function () { it("should revert", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await usd.approve(billBroker.target, usdFP("115000")); await billBroker.swapUSDForPerps(usdFP("115000"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); @@ -300,7 +321,7 @@ describe("BillBroker", function () { it("should return the swap amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await perp.approve(billBroker.target, perpFP("100000")); await billBroker.swapPerpsForUSD(perpFP("100000"), 0n); expect(await usd.balanceOf(billBroker.target)).to.eq(0n); @@ -309,7 +330,7 @@ describe("BillBroker", function () { billBroker, usdFP("100"), [0n, perpFP("100000"), priceFP("1"), priceFP("1")], - [perpFP("100"), 0n, 0n], + [perpFP("100"), 0n], ); }); }); @@ -322,7 +343,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], - [usdFP("115"), 0n, 0n], + [usdFP("115"), 0n], ); }); @@ -332,7 +353,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [usdFP("100"), 0n, 0n], + [usdFP("100"), 0n], ); }); @@ -342,7 +363,7 @@ describe("BillBroker", function () { billBroker, perpFP("14285"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [usdFP("14285"), 0n, 0n], + [usdFP("14285"), 0n], ); }); @@ -362,7 +383,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], - [usdFP("90"), 0n, 0n], + [usdFP("90"), 0n], ); }); @@ -370,81 +391,104 @@ describe("BillBroker", function () { it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await checkPerpToUSDSwapAmt( billBroker, perpFP("100"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], - [usdFP("103.5"), usdFP("11.5"), 0n], + [usdFP("103.5"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await checkPerpToUSDSwapAmt( billBroker, perpFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [usdFP("90"), usdFP("10"), 0n], + [usdFP("90"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await checkPerpToUSDSwapAmt( billBroker, perpFP("14285"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [usdFP("11142.474991"), usdFP("3142.525009"), 0n], + [usdFP("12427.993747"), 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await checkPerpToUSDSwapAmt( billBroker, perpFP("14286"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], - - [0n, 0n, 0n], + [0n, 0n], ); }); it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await checkPerpToUSDSwapAmt( billBroker, perpFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], - [usdFP("81"), usdFP("9"), 0n], + [usdFP("81.603396"), 0n], + ); + }); + + it("should return the perp amount and fees", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), + }, + usdToPerpSwapFeeFactors: { + lower: percFP("1.025"), + upper: percFP("1.2"), + }, + }); + await billBroker.updateARBounds( + [percFP("0.25"), percFP("4")], + [percFP("0.01"), percFP("100")], + ); + await checkPerpToUSDSwapAmt( + billBroker, + perpFP("50000"), + [usdFP("100000"), perpFP("1000"), priceFP("1"), priceFP("1")], + [usdFP("52271.287128"), 0n], ); }); }); @@ -453,9 +497,9 @@ describe("BillBroker", function () { it("should return the perp amount and fees", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -463,7 +507,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], - [usdFP("90"), usdFP("9"), usdFP("1")], + [usdFP("90"), usdFP("1")], ); }); }); @@ -471,7 +515,7 @@ describe("BillBroker", function () { describe("when the pool has only usd", function () { it("should return the swap amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await usd.approve(billBroker.target, usdFP("115000")); await billBroker.swapUSDForPerps(usdFP("115000"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); @@ -480,7 +524,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("100000"), 0n, priceFP("1"), priceFP("1")], - [usdFP("100"), 0n, 0n], + [usdFP("100"), 0n], ); }); }); @@ -488,7 +532,7 @@ describe("BillBroker", function () { describe("when the pool has only perps", function () { it("should return the swap amount", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await perp.approve(billBroker.target, perpFP("100000")); await billBroker.swapPerpsForUSD(perpFP("100000"), 0n); expect(await usd.balanceOf(billBroker.target)).to.eq(0n); @@ -587,9 +631,9 @@ describe("BillBroker", function () { it("should transfer usd from the user", async function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await expect(() => @@ -599,9 +643,9 @@ describe("BillBroker", function () { it("should transfer perps to the user", async function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await expect(() => @@ -611,9 +655,9 @@ describe("BillBroker", function () { it("should increase the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); expect(await assetRatio(billBroker)).to.eq(percFP("1")); @@ -623,9 +667,9 @@ describe("BillBroker", function () { it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await billBroker.swapUSDForPerps(usdFP("115"), perpFP("95")); @@ -636,9 +680,9 @@ describe("BillBroker", function () { it("should emit SwapUSDForPerps", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); const r = await billBroker.reserveState.staticCall(); @@ -652,9 +696,9 @@ describe("BillBroker", function () { it("should transfer usd from the user", async function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -667,9 +711,9 @@ describe("BillBroker", function () { setupContracts, ); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -683,9 +727,9 @@ describe("BillBroker", function () { it("should transfer protocol fee to the owner", async function () { const { billBroker, perp, feeCollector } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -697,9 +741,9 @@ describe("BillBroker", function () { it("should increase the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -710,9 +754,9 @@ describe("BillBroker", function () { it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -724,9 +768,9 @@ describe("BillBroker", function () { it("should emit SwapUSDForPerps", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -741,9 +785,9 @@ describe("BillBroker", function () { it("should transfer usd from the user", async function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await expect(() => @@ -753,9 +797,9 @@ describe("BillBroker", function () { it("should transfer perps to the user", async function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await expect(() => @@ -766,9 +810,9 @@ describe("BillBroker", function () { it("should increase the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); expect(await assetRatio(billBroker)).to.eq(percFP("1")); @@ -778,9 +822,9 @@ describe("BillBroker", function () { it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await billBroker.swapUSDForPerps(usdFP("3795"), perpFP("3130")); @@ -791,9 +835,9 @@ describe("BillBroker", function () { it("should emit SwapUSDForPerps", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); const r = await billBroker.reserveState.staticCall(); @@ -811,9 +855,9 @@ describe("BillBroker", function () { [percFP("0.75"), percFP("1.05")], ); await updateFees(billBroker, { - usdToPerpSwapFeePercs: { - lower: percFP("0.05"), - upper: percFP("0.5"), + usdToPerpSwapFeeFactors: { + lower: percFP("1.05"), + upper: percFP("1.2"), }, }); await expect( @@ -825,7 +869,7 @@ describe("BillBroker", function () { describe("when the pool has only usd", function () { it("should revert", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await usd.approve(billBroker.target, usdFP("115000")); await billBroker.swapUSDForPerps(usdFP("115000"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); @@ -839,7 +883,7 @@ describe("BillBroker", function () { describe("when the pool has only perps", function () { it("should execute swap", async function () { const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await perp.approve(billBroker.target, perpFP("100000")); await billBroker.swapPerpsForUSD(perpFP("100000"), 0n); expect(await usd.balanceOf(billBroker.target)).to.eq(0n); @@ -937,9 +981,9 @@ describe("BillBroker", function () { it("should transfer perps from the user", async function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await expect(() => @@ -949,9 +993,9 @@ describe("BillBroker", function () { it("should transfer usd to the user", async function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await expect(() => @@ -961,9 +1005,9 @@ describe("BillBroker", function () { it("should increase the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); expect(await assetRatio(billBroker)).to.eq(percFP("1")); @@ -973,9 +1017,9 @@ describe("BillBroker", function () { it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103")); @@ -986,9 +1030,9 @@ describe("BillBroker", function () { it("should emit SwapPerpsForUSD", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); const r = await billBroker.reserveState.staticCall(); @@ -1002,9 +1046,9 @@ describe("BillBroker", function () { it("should transfer perps from the user", async function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1017,9 +1061,9 @@ describe("BillBroker", function () { setupContracts, ); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1031,9 +1075,9 @@ describe("BillBroker", function () { it("should transfer protocol fee to the owner", async function () { const { billBroker, usd, feeCollector } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1045,9 +1089,9 @@ describe("BillBroker", function () { it("should increase the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1058,9 +1102,9 @@ describe("BillBroker", function () { it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1072,9 +1116,9 @@ describe("BillBroker", function () { it("should emit SwapPerpsForUSD", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1089,9 +1133,9 @@ describe("BillBroker", function () { it("should transfer perps from the user", async function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await expect(() => @@ -1101,9 +1145,9 @@ describe("BillBroker", function () { it("should transfer usd to the user", async function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await expect(() => @@ -1113,9 +1157,9 @@ describe("BillBroker", function () { it("should decrease the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); expect(await assetRatio(billBroker)).to.eq(percFP("1")); @@ -1125,9 +1169,9 @@ describe("BillBroker", function () { it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await billBroker.swapPerpsForUSD(perpFP("3600"), usdFP("3700")); @@ -1145,9 +1189,9 @@ describe("BillBroker", function () { [percFP("0.95"), percFP("1.25")], ); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, }); await expect( @@ -1157,9 +1201,9 @@ describe("BillBroker", function () { it("should emit SwapPerpsForUSD", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { - perpToUSDSwapFeePercs: { - lower: percFP("0.1"), - upper: percFP("0.5"), + perpToUSDSwapFeeFactors: { + lower: percFP("1.1"), + upper: percFP("1.2"), }, protocolSwapSharePerc: percFP("0.1"), }); @@ -1173,7 +1217,7 @@ describe("BillBroker", function () { describe("when the pool has only usd", function () { it("should execute swap", async function () { const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await usd.approve(billBroker.target, usdFP("115000")); await billBroker.swapUSDForPerps(usdFP("115000"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); @@ -1188,7 +1232,7 @@ describe("BillBroker", function () { describe("when the pool has only perps", function () { it("should revert", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); - await billBroker.updateARBounds([0n, ethers.MaxUint256], [0n, ethers.MaxUint256]); + await billBroker.updateARBounds([0n, ethers.MaxInt256], [0n, ethers.MaxInt256]); await perp.approve(billBroker.target, perpFP("100000")); await billBroker.swapPerpsForUSD(perpFP("100000"), 0n); expect(await usd.balanceOf(billBroker.target)).to.eq(0n); diff --git a/spot-vaults/test/LineHelpers.ts b/spot-vaults/test/LineHelpers.ts new file mode 100644 index 00000000..b3ec16d6 --- /dev/null +++ b/spot-vaults/test/LineHelpers.ts @@ -0,0 +1,310 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { Contract } from "ethers"; + +// AI generated +describe("LineHelpers", function () { + let testLineHelpers: Contract; + + // Deploy the test contract before running the suite + before(async () => { + const LineHelpersTester = await ethers.getContractFactory("LineHelpersTester"); + testLineHelpers = await LineHelpersTester.deploy(); + }); + + // + // Helper for constructing the `Line` struct in TypeScript + // + function makeLine(x1: number, y1: number, x2: number, y2: number) { + return { x1, y1, x2, y2 }; + } + + // + // Helper for constructing the `Range` struct in TypeScript + // + function makeRange(lower: number, upper: number) { + return { lower, upper }; + } + + // --------------------------------------------------------------------- + // 1. Tests for `avgY` + // --------------------------------------------------------------------- + describe("avgY", function () { + it("handles zero slope correctly", async () => { + const fn = makeLine(0, 100, 10, 100); // zero slope + const yAvg = await testLineHelpers.testAvgY(fn, 0, 10); + expect(yAvg).to.equal(100); + + // Subrange + const yAvgSub = await testLineHelpers.testAvgY(fn, 2, 5); + expect(yAvgSub).to.equal(100); + }); + + it("handles positive slope", async () => { + // slope = (200 - 100)/(10 - 0) = 10 + const fn = makeLine(0, 100, 10, 200); + // avg from x=0..10 => (100 + 200)/2 = 150 + const yAvg = await testLineHelpers.testAvgY(fn, 0, 10); + expect(yAvg).to.equal(150); + }); + + it("handles negative slope", async () => { + // slope = (50 - 100)/(10 - 0) = -5 + const fn = makeLine(0, 100, 10, 50); + // avg from x=0..10 => (100 + 50)/2 = 75 + const yAvg = await testLineHelpers.testAvgY(fn, 0, 10); + expect(yAvg).to.equal(75); + }); + + it("handles partial subrange", async () => { + // slope = 10 + // from x=2 => y=120, x=5 => y=150 => avg=135 + const fn = makeLine(0, 100, 10, 200); + const yAvg = await testLineHelpers.testAvgY(fn, 2, 5); + expect(yAvg).to.equal(135); + }); + }); + + // --------------------------------------------------------------------- + // 2. Tests for `computeY` + // --------------------------------------------------------------------- + describe("computeY", function () { + it("handles zero slope", async () => { + const fn = makeLine(0, 100, 10, 100); + const yAt5 = await testLineHelpers.testComputeY(fn, 5); + expect(yAt5).to.equal(100); + }); + + it("handles positive slope", async () => { + // slope=10 => line eq: y=10*x + 100 + const fn = makeLine(0, 100, 10, 200); + const yAt5 = await testLineHelpers.testComputeY(fn, 5); + expect(yAt5).to.equal(150); + }); + + it("handles negative slope", async () => { + // slope=-5 => line eq: y=-5*x + 100 + const fn = makeLine(0, 100, 10, 50); + const yAt2 = await testLineHelpers.testComputeY(fn, 2); + expect(yAt2).to.equal(90); + }); + }); + + // --------------------------------------------------------------------- + // 3. Tests for `computePiecewiseAvgY` + // --------------------------------------------------------------------- + describe("computePiecewiseAvgY", function () { + // Reusable lines and ranges in multiple tests + let fn1, fn2, fn3; + let xBreakPt; + + beforeEach(async () => { + fn1 = makeLine(0, 50, 10, 100); + fn2 = makeLine(0, 100, 10, 200); + fn3 = makeLine(0, 200, 10, 300); + xBreakPt = makeRange(2, 8); + }); + + it("reverts when xRange.lower > xRange.upper", async () => { + const xRange = makeRange(10, 5); // invalid + await expect( + testLineHelpers.testComputePiecewiseAvgY(fn1, fn2, fn3, xBreakPt, xRange), + ).to.be.revertedWithCustomError(testLineHelpers, "InvalidRange"); + }); + + it("reverts when xRange straddles from below to above breakpoints", async () => { + // crosses [2..8] from 1..9 + const xRange = makeRange(1, 9); + await expect( + testLineHelpers.testComputePiecewiseAvgY(fn1, fn2, fn3, xBreakPt, xRange), + ).to.be.revertedWithCustomError(testLineHelpers, "UnexpectedRangeDelta"); + }); + + it("uses fn1 entirely when xRange is below xBreakPt.lower", async () => { + // xBreakPt= [2..8], xRange= [0..1] + const localBreak = makeRange(2, 8); + const localRange = makeRange(0, 1); + const localFn1 = makeLine(0, 50, 10, 70); // slope ~ 2/unit + const localFn2 = makeLine(10, 60, 20, 200); + const localFn3 = makeLine(20, 200, 30, 500); + + const result = await testLineHelpers.testComputePiecewiseAvgY( + localFn1, + localFn2, + localFn3, + localBreak, + localRange, + ); + expect(result).to.equal(51); + }); + + it("uses fn3 entirely when xRange is above xBreakPt.upper", async () => { + // xBreakPt= [2..8], xRange= [9..10] + const localBreak = makeRange(2, 8); + const localRange = makeRange(9, 10); + + // fn3 from (8,200) to (10,300) + // y(9)=250, y(10)=300 => avg=275 + const localFn1 = makeLine(0, 0, 10, 0); + const localFn2 = makeLine(10, 0, 20, 100); + const localFn3 = makeLine(8, 200, 10, 300); + + const result = await testLineHelpers.testComputePiecewiseAvgY( + localFn1, + localFn2, + localFn3, + localBreak, + localRange, + ); + expect(result).to.equal(275); + }); + + it("splits range across fn1 & fn2 when straddling xBreakPt.lower", async () => { + // xBreakPt= [2..8], xRange= [1..5] + const localBreak = makeRange(2, 8); + const localRange = makeRange(1, 5); + + // fn1 => (0,10)->(2,30), slope=10 + // x=1 => y=20, x=2 => y=30 => avg=25 + // fn2 => (2,30)->(8,90), slope=10 + // x=2 => y=30, x=5 => y=60 => avg=45 + // Weighted sum => (25*1) + (45*3)=160 => /4=40 + const localFn1 = makeLine(0, 10, 2, 30); + const localFn2 = makeLine(2, 30, 8, 90); + const localFn3 = makeLine(8, 90, 10, 100); + + const result = await testLineHelpers.testComputePiecewiseAvgY( + localFn1, + localFn2, + localFn3, + localBreak, + localRange, + ); + expect(result).to.equal(40); + }); + + it("splits range across fn2 & fn3 when straddling xBreakPt.upper", async () => { + // xBreakPt= [2..8], xRange= [5..9] + const localBreak = makeRange(2, 8); + const localRange = makeRange(5, 9); + + // fn2 => (2,30)->(8,90), slope=10 + // x=5 => y=60, x=8 => y=90 => avg=75 + // fn3 => (8,90)->(10,110), slope=10 + // x=8 => y=90, x=9 => y=100 => avg=95 + // Weighted sum => (75*3)+(95*1)=320 => /4=80 + const localFn1 = makeLine(0, 10, 2, 30); + const localFn2 = makeLine(2, 30, 8, 90); + const localFn3 = makeLine(8, 90, 10, 110); + + const result = await testLineHelpers.testComputePiecewiseAvgY( + localFn1, + localFn2, + localFn3, + localBreak, + localRange, + ); + expect(result).to.equal(80); + }); + + it("uses only fn2 when xRange is fully within breakpoints", async () => { + // xBreakPt= [2..8], xRange= [3..7] + // fn2 => (2,20)->(8,80), slope=10 + // x=3 => y=30, x=7 => y=70 => avg=50 + const localBreak = makeRange(2, 8); + const localRange = makeRange(3, 7); + + const localFn1 = makeLine(0, 10, 2, 20); + const localFn2 = makeLine(2, 20, 8, 80); + const localFn3 = makeLine(8, 80, 10, 100); + + const result = await testLineHelpers.testComputePiecewiseAvgY( + localFn1, + localFn2, + localFn3, + localBreak, + localRange, + ); + expect(result).to.equal(50); + }); + + it("handles zero-length xRange exactly at xBreakPt.lower", async () => { + // xRange= [2..2] => a single x-value + // Expect to take fn1 vs fn2? + // Typically, a single x==2 is the boundary => By definition, + // if `upper <= breakPt.lower` => we are in fn1 + // OR if `lower >= breakPt.lower` => we might be in fn2. + // This might require clarity in your design or the function. + // For demonstration, let's assume we define: + // if x == bpl => treat as fn2 (since "below" is strictly < bpl). + const localRange = makeRange(2, 2); + // We'll set the lines so we can easily compute y(2). + const localFn1 = makeLine(0, 10, 2, 30); // y(2)=30 + const localFn2 = makeLine(2, 30, 8, 90); // y(2)=30 + // We'll see which one the code picks based on your piecewise logic + // For a zero-length range, the "average" is just y(2). + + const result = await testLineHelpers.testComputePiecewiseAvgY( + localFn1, + localFn2, + fn3, + xBreakPt, + localRange, + ); + // Depending on your code logic: + // If your code lumps x=breakPt.lower in fn1, expect 30 + // If lumps it in fn2, also 30 in this example (coincidentally the same). + // If you want to ensure it's definitely fn2, you could change lines: + // localFn2 = makeLine(2, 50, 8, 90) => y(2)=50 + // Then check if result==50 => means it's definitely fn2. + expect(result).to.equal(30); + }); + + it("handles zero-length xRange exactly at xBreakPt.upper", async () => { + // xBreakPt= [2..8], xRange= [8..8] + // Single point at x=8 + // By your design, x=8 could be considered "upper edge" of fn2 or "start" of fn3. + const localRange = makeRange(8, 8); + + // Distinguish the lines so we can confirm which is used + const localFn2 = makeLine(2, 20, 8, 80); // y(8)=80 + const localFn3 = makeLine(8, 999, 10, 1000); // y(8)=999 + + const result = await testLineHelpers.testComputePiecewiseAvgY( + fn1, + localFn2, + localFn3, + xBreakPt, + localRange, + ); + // Depending on how your piecewise function is coded: + // - If x=breakPt.upper is still "within" fn2 => expect 80 + // - If your code lumps it with fn3 => expect 999 + // In *your* logic, it looks like "if (xRange.lower <= bpu && xRange.upper > bpu)" + // is the condition for partial in fn2/fn3. + // But here, 8..8 is exactly <=bpu and not >bpu => might remain in fn2 + // So I'd expect 80 given the code snippet above. + expect(result).to.equal(80); + }); + + it("uses only fn2 when xRange = xBreakPt exactly", async () => { + // xRange= [2..8], which is exactly the break range + // Should use fn2 entirely, no partial + const localRange = makeRange(2, 8); + // We'll define fn2 so we can test the integral average from 2..8 + // Let's pick slope=10 again => + // y(2)=20, y(8)=80 => average => (20+80)/2=50 + const localFn2 = makeLine(2, 20, 8, 80); + + const result = await testLineHelpers.testComputePiecewiseAvgY( + fn1, + localFn2, + fn3, + xBreakPt, + localRange, + ); + // If the code lumps [2..8] wholly into fn2, we get 50 + expect(result).to.equal(50); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 0ac2c2f5..9fad116d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -157,7 +157,7 @@ __metadata: ethers: ^6.6.0 ethers-v5: "npm:ethers@^5.7.0" ganache-cli: latest - hardhat: ^2.22.10 + hardhat: ^2.22.18 hardhat-gas-reporter: latest lodash: ^4.17.21 prettier: ^2.7.1 @@ -1341,10 +1341,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-arm64@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.5.2" - checksum: f6ab386603c6e080fe7f611b739eb6d1d6a370220318b725cb582702563577150b3be14b6d0be71cb72bdb854e6992c587ecfc6833216f750eae8e7cd96de46f +"@nomicfoundation/edr-darwin-arm64@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.7.0" + checksum: 15541029227f65df2cf1df93343f6c8e9484494ddf26cf12289cea7410446ce5b2ebb9ac750a7d438276c427da7678aba0fd8fda33ed7c1e8c743955fb657055 languageName: node linkType: hard @@ -1355,10 +1355,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-x64@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-darwin-x64@npm:0.5.2" - checksum: 6f91f6d0294c0450e0501983f1de34a48582fe93f48428bc4b798ac93bb5483a96d626c2b4c62ac91102f00c826a3f9bfa16d748301440ebe1bbb2920ba3ba6d +"@nomicfoundation/edr-darwin-x64@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.7.0" + checksum: 4b7cbbe6e6b0484805814ca7f2151de780dfc6102d443126666a4e5a19955343cb6e709c0f99b3a80b0b07fce50d5c2cee9958c516a1c2b0f2ac568a30db1712 languageName: node linkType: hard @@ -1369,10 +1369,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-gnu@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.5.2" - checksum: bd84cc2741bb2be3c3a60bae9dbb8ca7794a68b8675684c97f2c6e7310e5cba7efd1cf18d392d42481cda83fb478f83c0bd605104c5cf08bab44ec07669c3010 +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.7.0" + checksum: 836786bbe5f9ee9fce220c7177fd5f6565b4049e42ca8163d2676c01b211aa7c688d6662cf2e469ba44147bfeaf2152f5cc168aaba171af1507477511f3289cc languageName: node linkType: hard @@ -1383,10 +1383,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-musl@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.5.2" - checksum: e7f7d82f16be1b26805bd90964c456aecb4a6a1397e87d507810d37bd383064271fa63932564e725fdb30868925334338ec459fe32f84fc11206644b7b37825c +"@nomicfoundation/edr-linux-arm64-musl@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.7.0" + checksum: 005faee82c11965430a72eaa8a55d87db0ea8b149b6d0f483b505e099152c7148a959412c387e143efc0fd51fb2221dae4e1d7004c83a9f323819db1049713f7 languageName: node linkType: hard @@ -1397,10 +1397,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-gnu@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.5.2" - checksum: ec025bf75227638b6b2cd01b7ba01b3ddaddf54c4d18d25e9d0364ac621981be2aaf124f4e60a88da5c9e267adb41a660a42668e2d6c9a6a57e55e8671fc76f1 +"@nomicfoundation/edr-linux-x64-gnu@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.7.0" + checksum: 18b60a0bbb7ad35240fc5f4bbe62cd83cdbc0c3e6a513d3bca3c291874d9cb02da7eb55ec3af0d8e273060347d588f80b7fbf304704c2c4cf9f7b302d4d1b466 languageName: node linkType: hard @@ -1411,10 +1411,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-musl@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.5.2" - checksum: c9ff47f72645492383b2a598675878abc029b86325e2c457db1b2c4281916e11e4ef6336c355d40ae3c1736595bc43da51cfcf1e923464011f526f4db64c245b +"@nomicfoundation/edr-linux-x64-musl@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.7.0" + checksum: 84e4cc834f71db02e21ea218919e9cec67983894372c232d5f36519d2d5579268d0019eb4f548826bfd1677867c78a0db81fce26bced330b375da68c91458a38 languageName: node linkType: hard @@ -1425,10 +1425,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-win32-x64-msvc@npm:0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.5.2" - checksum: 56da7a1283470dede413cda5f2fef96e10250ec7a25562254ca0cd8045a653212c91e40fbcf37330e7af4e036d3c3aed83ea617831f9c7a5424389c73c53ed4e +"@nomicfoundation/edr-win32-x64-msvc@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.7.0" + checksum: 9f070a202cecab2d1f28353e5d90cd113ef98b9473169c83b97ab140898bd47a301a0cae69733346fe4cc57c31aa6f3796ab3280f1316aa79d561fecd00cc222 languageName: node linkType: hard @@ -1447,18 +1447,18 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr@npm:^0.5.2": - version: 0.5.2 - resolution: "@nomicfoundation/edr@npm:0.5.2" +"@nomicfoundation/edr@npm:^0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr@npm:0.7.0" dependencies: - "@nomicfoundation/edr-darwin-arm64": 0.5.2 - "@nomicfoundation/edr-darwin-x64": 0.5.2 - "@nomicfoundation/edr-linux-arm64-gnu": 0.5.2 - "@nomicfoundation/edr-linux-arm64-musl": 0.5.2 - "@nomicfoundation/edr-linux-x64-gnu": 0.5.2 - "@nomicfoundation/edr-linux-x64-musl": 0.5.2 - "@nomicfoundation/edr-win32-x64-msvc": 0.5.2 - checksum: 336b1c7cad96fa78410f0c9cc9524abe9fb1e56384fe990b98bfd17f15f25b4665ad8f0525ccd9511f7c19173841fe712d50db993078629e2fc4047fda4665dc + "@nomicfoundation/edr-darwin-arm64": 0.7.0 + "@nomicfoundation/edr-darwin-x64": 0.7.0 + "@nomicfoundation/edr-linux-arm64-gnu": 0.7.0 + "@nomicfoundation/edr-linux-arm64-musl": 0.7.0 + "@nomicfoundation/edr-linux-x64-gnu": 0.7.0 + "@nomicfoundation/edr-linux-x64-musl": 0.7.0 + "@nomicfoundation/edr-win32-x64-msvc": 0.7.0 + checksum: 83c54e2e2815c57ce56262f1010a0c5a2917b366bcf0acfad7662cbf2ccf46fd281e66c6db67bca9db7d91f699254216708045e882fda08c81361f111a07cb48 languageName: node linkType: hard @@ -5323,6 +5323,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^4.0.0": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: ^4.0.1 + checksum: a8765e452bbafd04f3f2fad79f04222dd65f43161488bb6014a41099e6ca18d166af613d59a90771908c1c823efa3f46ba36b86ac50b701c20c1b9908c5fe36e + languageName: node + linkType: hard + "chownr@npm:^1.0.1, chownr@npm:^1.1.4": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -8207,6 +8216,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.4.2": + version: 6.4.2 + resolution: "fdir@npm:6.4.2" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 517ad31c495f1c0778238eef574a7818788efaaf2ce1969ffa18c70793e2951a9763dfa2e6720b8fcef615e602a3cbb47f9b8aea9da0b02147579ab36043f22f + languageName: node + linkType: hard + "fetch-ponyfill@npm:^4.0.0": version: 4.1.0 resolution: "fetch-ponyfill@npm:4.1.0" @@ -9321,13 +9342,13 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.22.10": - version: 2.22.10 - resolution: "hardhat@npm:2.22.10" +"hardhat@npm:^2.22.18": + version: 2.22.18 + resolution: "hardhat@npm:2.22.18" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 - "@nomicfoundation/edr": ^0.5.2 + "@nomicfoundation/edr": ^0.7.0 "@nomicfoundation/ethereumjs-common": 4.0.4 "@nomicfoundation/ethereumjs-tx": 5.0.4 "@nomicfoundation/ethereumjs-util": 9.0.4 @@ -9339,31 +9360,32 @@ __metadata: aggregate-error: ^3.0.0 ansi-escapes: ^4.3.0 boxen: ^5.1.2 - chalk: ^2.4.2 - chokidar: ^3.4.0 + chokidar: ^4.0.0 ci-info: ^2.0.0 debug: ^4.1.1 enquirer: ^2.3.0 env-paths: ^2.2.0 ethereum-cryptography: ^1.0.3 ethereumjs-abi: ^0.6.8 - find-up: ^2.1.0 + find-up: ^5.0.0 fp-ts: 1.19.3 fs-extra: ^7.0.1 - glob: 7.2.0 immutable: ^4.0.0-rc.12 io-ts: 1.10.4 + json-stream-stringify: ^3.1.4 keccak: ^3.0.2 lodash: ^4.17.11 mnemonist: ^0.38.0 mocha: ^10.0.0 p-map: ^4.0.0 + picocolors: ^1.1.0 raw-body: ^2.4.1 resolve: 1.17.0 semver: ^6.3.0 solc: 0.8.26 source-map-support: ^0.5.13 stacktrace-parser: ^0.1.10 + tinyglobby: ^0.2.6 tsort: 0.0.1 undici: ^5.14.0 uuid: ^8.3.2 @@ -9378,7 +9400,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 2bb961a11f428fd025f990ea18472f4197c8352dd81f4231f27c04b7a8e94bc71d668262475102ae2c339ad83dd0e759b90ac7e4905f043be7bde471c04b5951 + checksum: e350e80a96846a465e1ca0c92a30a83e5a04225b8def19c9703d049f4a05ac69ff12150f93bf647e3ce3f82e2264558c6a2cec1b8e8a8494b97ffbf241f54077 languageName: node linkType: hard @@ -10850,6 +10872,13 @@ __metadata: languageName: node linkType: hard +"json-stream-stringify@npm:^3.1.4": + version: 3.1.6 + resolution: "json-stream-stringify@npm:3.1.6" + checksum: ce873e09fe18461960b7536f63e2f913a2cb242819513856ed1af58989d41846976e7177cb1fe3c835220023aa01e534d56b6d5c3290a5b23793a6f4cb93785e + languageName: node + linkType: hard + "json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" @@ -13389,6 +13418,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.1.0": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -13396,6 +13432,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: a7a5188c954f82c6585720e9143297ccd0e35ad8072231608086ca950bee672d51b0ef676254af0788205e59bd4e4deb4e7708769226bed725bf13370a7d1464 + languageName: node + linkType: hard + "pify@npm:^2.0.0, pify@npm:^2.3.0": version: 2.3.0 resolution: "pify@npm:2.3.0" @@ -13958,6 +14001,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.0.2 + resolution: "readdirp@npm:4.0.2" + checksum: 309376e717f94fb7eb61bec21e2603243a9e2420cd2e9bf94ddf026aefea0d7377ed1a62f016d33265682e44908049a55c3cfc2307450a1421654ea008489b39 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -15846,6 +15896,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.6": + version: 0.2.10 + resolution: "tinyglobby@npm:0.2.10" + dependencies: + fdir: ^6.4.2 + picomatch: ^4.0.2 + checksum: 7e2ffe262ebc149036bdef37c56b32d02d52cf09efa7d43dbdab2ea3c12844a4da881058835ce4c74d1891190e5ad5ec5133560a11ec8314849b68ad0d99d3f4 + languageName: node + linkType: hard + "tmp-promise@npm:^3.0.2": version: 3.0.3 resolution: "tmp-promise@npm:3.0.3"