diff --git a/package.json b/package.json index fcd9b7b4..115af386 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.3.0", + "version": "1.3.1", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": { diff --git a/script/deploy/helpers/Logger.sol b/script/deploy/helpers/Logger.sol index 39b8778c..f7d74e67 100644 --- a/script/deploy/helpers/Logger.sol +++ b/script/deploy/helpers/Logger.sol @@ -8,7 +8,9 @@ import {console} from "forge-std/console.sol"; import {Well} from "src/Well.sol"; library logger { - function logWell(Well well) public view { + function logWell( + Well well + ) public view { console.log("\nWELL:", address(well)); console.log("Name \t", well.name()); console.log("Symbol\t", well.symbol()); diff --git a/src/Well.sol b/src/Well.sol index f14793f4..163379a9 100644 --- a/src/Well.sol +++ b/src/Well.sol @@ -408,7 +408,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr } } - function getShiftOut(IERC20 tokenOut) external view readOnlyNonReentrant returns (uint256 amountOut) { + function getShiftOut( + IERC20 tokenOut + ) external view readOnlyNonReentrant returns (uint256 amountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = new uint256[](tokensLength); @@ -674,7 +676,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Transfer excess tokens held by the Well to `recipient`. */ - function skim(address recipient) external nonReentrant returns (uint256[] memory skimAmounts) { + function skim( + address recipient + ) external nonReentrant returns (uint256[] memory skimAmounts) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); @@ -694,7 +698,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Gets the Well's token reserves by reading from byte storage. */ - function _getReserves(uint256 _numberOfTokens) internal view returns (uint256[] memory reserves) { + function _getReserves( + uint256 _numberOfTokens + ) internal view returns (uint256[] memory reserves) { reserves = LibBytes.readUint128(RESERVES_STORAGE_SLOT, _numberOfTokens); } @@ -717,7 +723,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr * @dev Fetches the current token reserves of the Well and updates the Pumps. * Typically called before an operation that modifies the Well's reserves. */ - function _updatePumps(uint256 _numberOfTokens) internal returns (uint256[] memory reserves) { + function _updatePumps( + uint256 _numberOfTokens + ) internal returns (uint256[] memory reserves) { reserves = _getReserves(_numberOfTokens); uint256 _numberOfPumps = numberOfPumps(); @@ -861,7 +869,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Reverts if the deadline has passed. */ - modifier expire(uint256 deadline) { + modifier expire( + uint256 deadline + ) { if (block.timestamp > deadline) { revert Expired(); } diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index 171877e1..e12d8a77 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -60,7 +60,9 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * @notice Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an ERC1167 minimal proxy from an aquifier, pointing to a well implmentation. */ - function _authorizeUpgrade(address newImplementation) internal view override onlyOwner { + function _authorizeUpgrade( + address newImplementation + ) internal view override onlyOwner { // verify the function is called through a delegatecall. require(address(this) != ___self, "Function must be called through delegatecall"); @@ -96,7 +98,9 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies * cloned (Bored) by an Aquifer. */ - function upgradeTo(address newImplementation) public override { + function upgradeTo( + address newImplementation + ) public override { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); } diff --git a/src/functions/ConstantProduct2.sol b/src/functions/ConstantProduct2.sol index d99800b9..c44e9449 100644 --- a/src/functions/ConstantProduct2.sol +++ b/src/functions/ConstantProduct2.sol @@ -82,7 +82,7 @@ contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction { } function version() external pure override returns (string memory) { - return "1.2.0"; + return "1.2.1"; } /// @dev `b_j = (b_0 * b_1 * r_j / r_i)^(1/2)` @@ -107,7 +107,7 @@ contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction { bytes calldata ) external pure override returns (uint256 reserve) { uint256 i = j == 1 ? 0 : 1; - reserve = reserves[i] * ratios[j] / ratios[i]; + reserve = reserves[i].mulDiv(ratios[j], ratios[i]); } function calcRate( @@ -116,7 +116,7 @@ contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 j, bytes calldata ) external pure returns (uint256 rate) { - return reserves[i] * CALC_RATE_PRECISION / reserves[j]; + rate = reserves[i].mulDiv(CALC_RATE_PRECISION, reserves[j]); } /** diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index 9a78d7c3..a1281749 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -24,7 +24,9 @@ contract Stable2LUT1 is ILookupTable { * @notice Returns the estimated range of reserve ratios for a given price, * assuming one token reserve remains constant. */ - function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) { + function getRatiosFromPriceLiquidity( + uint256 price + ) external pure returns (PriceData memory) { if (price < 1.006758e6) { if (price < 0.885627e6) { if (price < 0.59332e6) { @@ -737,7 +739,9 @@ contract Stable2LUT1 is ILookupTable { * @notice Returns the estimated range of reserve ratios for a given price, * assuming the pool liquidity remains constant. */ - function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) { + function getRatiosFromPriceSwap( + uint256 price + ) external pure returns (PriceData memory) { if (price < 0.993344e6) { if (price < 0.834426e6) { if (price < 0.718073e6) { diff --git a/src/interfaces/IAquifer.sol b/src/interfaces/IAquifer.sol index a9ab0090..bd671e9a 100644 --- a/src/interfaces/IAquifer.sol +++ b/src/interfaces/IAquifer.sol @@ -65,5 +65,7 @@ interface IAquifer { * @dev Always verify that a Well was deployed by a trusted Aquifer using a trusted implementation before using. * If `wellImplementation == address(0)`, then the Aquifer did not deploy the Well. */ - function wellImplementation(address well) external view returns (address implementation); + function wellImplementation( + address well + ) external view returns (address implementation); } diff --git a/src/interfaces/ILookupTable.sol b/src/interfaces/ILookupTable.sol index 6d774852..eec8805f 100644 --- a/src/interfaces/ILookupTable.sol +++ b/src/interfaces/ILookupTable.sol @@ -29,7 +29,11 @@ interface ILookupTable { uint256 precision; } - function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); - function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); + function getRatiosFromPriceLiquidity( + uint256 + ) external view returns (PriceData memory); + function getRatiosFromPriceSwap( + uint256 + ) external view returns (PriceData memory); function getAParameter() external view returns (uint256); } diff --git a/src/interfaces/IWell.sol b/src/interfaces/IWell.sol index 1b734108..e96a6b0b 100644 --- a/src/interfaces/IWell.sol +++ b/src/interfaces/IWell.sol @@ -238,7 +238,9 @@ interface IWell { * @param tokenOut The token to shift into * @return amountOut The amount of `tokenOut` received */ - function getShiftOut(IERC20 tokenOut) external returns (uint256 amountOut); + function getShiftOut( + IERC20 tokenOut + ) external returns (uint256 amountOut); //////////////////// ADD LIQUIDITY //////////////////// @@ -279,7 +281,9 @@ interface IWell { * @param tokenAmountsIn The amount of each token to add; MUST match the indexing of {Well.tokens} * @return lpAmountOut The amount of LP tokens received */ - function getAddLiquidityOut(uint256[] memory tokenAmountsIn) external view returns (uint256 lpAmountOut); + function getAddLiquidityOut( + uint256[] memory tokenAmountsIn + ) external view returns (uint256 lpAmountOut); //////////////////// REMOVE LIQUIDITY: BALANCED //////////////////// @@ -303,7 +307,9 @@ interface IWell { * @param lpAmountIn The amount of LP tokens to burn * @return tokenAmountsOut The amount of each underlying token received */ - function getRemoveLiquidityOut(uint256 lpAmountIn) external view returns (uint256[] memory tokenAmountsOut); + function getRemoveLiquidityOut( + uint256 lpAmountIn + ) external view returns (uint256[] memory tokenAmountsOut); //////////////////// REMOVE LIQUIDITY: ONE TOKEN //////////////////// @@ -388,7 +394,9 @@ interface IWell { * @return skimAmounts The amount of each token skimmed * @dev No deadline is needed since this function does not use the user's assets. */ - function skim(address recipient) external returns (uint256[] memory skimAmounts); + function skim( + address recipient + ) external returns (uint256[] memory skimAmounts); /** * @notice Gets the reserves of each token held by the Well. diff --git a/src/libraries/LibContractInfo.sol b/src/libraries/LibContractInfo.sol index e7663ddc..9ada8ab1 100644 --- a/src/libraries/LibContractInfo.sol +++ b/src/libraries/LibContractInfo.sol @@ -13,7 +13,9 @@ library LibContractInfo { * @return symbol The symbol of the contract * @dev if the contract does not have a symbol function, the first 4 bytes of the address are returned */ - function getSymbol(address _contract) internal view returns (string memory symbol) { + function getSymbol( + address _contract + ) internal view returns (string memory symbol) { (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("symbol()")); symbol = new string(4); if (success) { @@ -31,7 +33,9 @@ library LibContractInfo { * @return name The name of the contract * @dev if the contract does not have a name function, the first 8 bytes of the address are returned */ - function getName(address _contract) internal view returns (string memory name) { + function getName( + address _contract + ) internal view returns (string memory name) { (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("name()")); name = new string(8); if (success) { @@ -49,7 +53,9 @@ library LibContractInfo { * @return decimals The decimals of the contract * @dev if the contract does not have a decimals function, 18 is returned */ - function getDecimals(address _contract) internal view returns (uint8 decimals) { + function getDecimals( + address _contract + ) internal view returns (uint8 decimals) { (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("decimals()")); decimals = success ? abi.decode(data, (uint8)) : 18; // default to 18 decimals } diff --git a/src/libraries/LibLastReserveBytes.sol b/src/libraries/LibLastReserveBytes.sol index cfd43a34..7b268751 100644 --- a/src/libraries/LibLastReserveBytes.sol +++ b/src/libraries/LibLastReserveBytes.sol @@ -20,7 +20,9 @@ library LibLastReserveBytes { using ABDKMathQuad for uint256; using ABDKMathQuad for bytes16; - function readNumberOfReserves(bytes32 slot) internal view returns (uint8 _numberOfReserves) { + function readNumberOfReserves( + bytes32 slot + ) internal view returns (uint8 _numberOfReserves) { assembly { _numberOfReserves := shr(248, sload(slot)) } @@ -137,7 +139,17 @@ library LibLastReserveBytes { } } - function readBytes(bytes32 slot) internal view returns (bytes32 value) { + function resetLastReserves(bytes32 slot, uint256 n) internal { + for (uint256 i; i < (n + 1) / 2; ++i) { + assembly { + sstore(add(slot, i), 0) + } + } + } + + function readBytes( + bytes32 slot + ) internal view returns (bytes32 value) { assembly { value := sload(slot) } diff --git a/src/libraries/LibMath.sol b/src/libraries/LibMath.sol index c291c69f..368db292 100644 --- a/src/libraries/LibMath.sol +++ b/src/libraries/LibMath.sol @@ -68,7 +68,9 @@ library LibMath { * Implementation from: https://github.com/Gaussian-Process/solidity-sqrt/blob/main/src/FixedPointMathLib.sol * based on https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol */ - function sqrt(uint256 a) internal pure returns (uint256 z) { + function sqrt( + uint256 a + ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { let y := a // We start y at a, which will help us make our initial estimate. diff --git a/src/pumps/MultiFlowPump.sol b/src/pumps/MultiFlowPump.sol index bc8b48e1..c27e3943 100644 --- a/src/pumps/MultiFlowPump.sol +++ b/src/pumps/MultiFlowPump.sol @@ -91,6 +91,11 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu return; } + if (reserves[0] == 0 && reserves[1] == 0) { + slot.resetLastReserves(numberOfReserves); + return; + } + bytes16 alphaN; bytes16 deltaTimestampBytes; uint256 capExponent; @@ -107,6 +112,12 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu pumpState.lastReserves = _capReserves(msg.sender, pumpState.lastReserves, reserves, capExponent, crp); + // If the last reserves have been manipulated to 0, reset the pump. + if (pumpState.lastReserves[0] == 0 && pumpState.lastReserves[1] == 0) { + slot.resetLastReserves(numberOfReserves); + return; + } + // Read: Cumulative & EMA Reserves // Start at the slot after `pumpState.lastReserves` uint256 numSlots = _getSlotsOffset(numberOfReserves); @@ -265,6 +276,12 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu crv.rLast = tryCalcRate(mfpWf, lastReserves, i, j, data); crv.r = tryCalcRate(mfpWf, cappedReserves, i, j, data); + // The ratio can only be infinite due to unintended behavior. + // Therefore, to get the reseves back to a normal state, return the current reserves. + if (crv.rLast == type(uint256).max) { + return reserves; + } + // If the ratio increased, check that it didn't increase above the max. if (crv.r > crv.rLast) { bytes16 tempExp = ABDKMathQuad.ONE.add(crp.maxRateChanges[i][j]).powu(capExponent); @@ -318,7 +335,7 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu if (returnIfBelowMin) return new uint256[](0); cappedReserves = tryCalcLPTokenUnderlying(mfpWf, maxLpTokenSupply, cappedReserves, lpTokenSupply, data); } - // If LP Token Suppply decreased, check that it didn't decrease below the min. + // If LP Token Supply decreased, check that it didn't decrease below the min. } else if (lpTokenSupply < lastLpTokenSupply) { uint256 minLpTokenSupply = lastLpTokenSupply * (ABDKMathQuad.ONE.sub(crp.maxLpSupplyDecrease)).powu(capExponent).to128x128().toUint256() / CAP_PRECISION2; @@ -541,7 +558,7 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu try wf.calcReserveAtRatioSwap(reserves, i, ratios, data) returns (uint256 _reserve) { reserve = _reserve; } catch { - reserve = type(uint256).max; + reserve = type(uint128).max; } } @@ -595,15 +612,10 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu uint256[] memory _underlyingAmounts ) { underlyingAmounts = _underlyingAmounts; - for (uint256 i; i < underlyingAmounts.length; ++i) { - if (underlyingAmounts[i] == 0) { - underlyingAmounts[i] = 1; - } - } } catch { underlyingAmounts = new uint256[](reserves.length); for (uint256 i; i < reserves.length; ++i) { - underlyingAmounts[i] = type(uint256).max; + underlyingAmounts[i] = 0; } } } diff --git a/test/Stable2/Well.Stable2.RemoveLiquidity.t.sol b/test/Stable2/Well.Stable2.RemoveLiquidity.t.sol index 1e2dbfc0..8522cec9 100644 --- a/test/Stable2/Well.Stable2.RemoveLiquidity.t.sol +++ b/test/Stable2/Well.Stable2.RemoveLiquidity.t.sol @@ -81,7 +81,9 @@ contract WellStable2RemoveLiquidityTest is LiquidityHelper { /// @dev Fuzz test: EQUAL token reserves, BALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { + function test_removeLiquidity_fuzz( + uint256 a0 + ) public prank(user) { // Setup amounts of liquidity to remove // NOTE: amounts may or may not match the maximum removable by `user`. uint256[] memory amounts = new uint256[](2); diff --git a/test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol b/test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol index 6ca26382..3d0574c7 100644 --- a/test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol +++ b/test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol @@ -78,7 +78,9 @@ contract WellStable2RemoveLiquidityOneTokenTest is TestHelper { /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { + function testFuzz_removeLiquidityOneToken( + uint256 a0 + ) public prank(user) { // Assume we're removing tokens[0] uint256[] memory amounts = new uint256[](2); amounts[0] = bound(a0, 1e18, 750e18); diff --git a/test/Stable2/Well.Stable2.Shift.t.sol b/test/Stable2/Well.Stable2.Shift.t.sol index e2873e84..f0c29d51 100644 --- a/test/Stable2/Well.Stable2.Shift.t.sol +++ b/test/Stable2/Well.Stable2.Shift.t.sol @@ -13,7 +13,9 @@ contract WellStable2ShiftTest is TestHelper { } /// @dev Shift excess token0 into token1. - function testFuzz_shift(uint256 amount) public prank(user) { + function testFuzz_shift( + uint256 amount + ) public prank(user) { amount = bound(amount, 1, 1000e18); // Transfer `amount` of token0 to the Well @@ -67,7 +69,9 @@ contract WellStable2ShiftTest is TestHelper { } /// @dev Shift excess token0 into token0 (just transfers the excess token0 to the user). - function testFuzz_shift_tokenOut(uint256 amount) public prank(user) { + function testFuzz_shift_tokenOut( + uint256 amount + ) public prank(user) { amount = bound(amount, 1, 1000e18); // Transfer `amount` of token0 to the Well @@ -144,7 +148,9 @@ contract WellStable2ShiftTest is TestHelper { checkInvariant(address(well)); } - function test_shift_fail_slippage(uint256 amount) public prank(user) { + function test_shift_fail_slippage( + uint256 amount + ) public prank(user) { amount = bound(amount, 1, 1000e18); // Transfer `amount` of token0 to the Well diff --git a/test/Stable2/Well.Stable2.Skim.t.sol b/test/Stable2/Well.Stable2.Skim.t.sol index 190f1cc0..fa081812 100644 --- a/test/Stable2/Well.Stable2.Skim.t.sol +++ b/test/Stable2/Well.Stable2.Skim.t.sol @@ -15,7 +15,9 @@ contract WellStable2SkimTest is TestHelper { assertEq(wellBalance.tokens[1], 1000e18); } - function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { + function testFuzz_skim( + uint256[2] calldata amounts + ) public prank(user) { vm.assume(amounts[0] <= 800e18); vm.assume(amounts[1] <= 800e18); diff --git a/test/Stable2/Well.Stable2.SwapFrom.t.sol b/test/Stable2/Well.Stable2.SwapFrom.t.sol index 436e421a..56107770 100644 --- a/test/Stable2/Well.Stable2.SwapFrom.t.sol +++ b/test/Stable2/Well.Stable2.SwapFrom.t.sol @@ -69,7 +69,9 @@ contract WellStable2SwapFromTest is SwapHelper { well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); } - function testFuzz_swapFrom(uint256 amountIn) public prank(user) { + function testFuzz_swapFrom( + uint256 amountIn + ) public prank(user) { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); @@ -78,7 +80,9 @@ contract WellStable2SwapFromTest is SwapHelper { checkStableSwapInvariant(address(well)); } - function testFuzz_swapAndRemoveAllLiq(uint256 amountIn) public { + function testFuzz_swapAndRemoveAllLiq( + uint256 amountIn + ) public { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); vm.prank(user); well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); @@ -91,7 +95,9 @@ contract WellStable2SwapFromTest is SwapHelper { } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapFrom_equalSwap(uint256 token0AmtIn) public prank(user) { + function testFuzz_swapFrom_equalSwap( + uint256 token0AmtIn + ) public prank(user) { vm.assume(token0AmtIn < tokens[0].balanceOf(user)); uint256 token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint256).max); uint256 token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint256).max); diff --git a/test/Stable2/Well.Stable2.SwapTo.t.sol b/test/Stable2/Well.Stable2.SwapTo.t.sol index 067b5d02..b7fd1d74 100644 --- a/test/Stable2/Well.Stable2.SwapTo.t.sol +++ b/test/Stable2/Well.Stable2.SwapTo.t.sol @@ -20,7 +20,9 @@ contract WellStable2SwapToTest is SwapHelper { assertEq(amountIn, 103_464_719_546_263_310_322); // ~3% slippage } - function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { + function testFuzz_getSwapIn_revertIf_insufficientWellBalance( + uint256 i + ) public prank(user) { IERC20[] memory _tokens = well.tokens(); Balances memory wellBalances = getBalances(address(well), well); vm.assume(i < _tokens.length); @@ -60,7 +62,9 @@ contract WellStable2SwapToTest is SwapHelper { } /// @dev tests assume 2 tokens in future we can extend for multiple tokens - function testFuzz_swapTo(uint256 amountOut) public prank(user) { + function testFuzz_swapTo( + uint256 amountOut + ) public prank(user) { // User has 1000 of each token // Given current liquidity, swapping 1000 of one token gives 500 of the other uint256 maxAmountIn = 1000 * 1e18; @@ -100,7 +104,9 @@ contract WellStable2SwapToTest is SwapHelper { } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { + function testFuzz_swapTo_equalSwap( + uint256 token0AmtOut + ) public prank(user) { // assume amtOut is lower due to slippage vm.assume(token0AmtOut < 500e18); uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); diff --git a/test/SwapHelper.sol b/test/SwapHelper.sol index 94738b88..924dcf92 100644 --- a/test/SwapHelper.sol +++ b/test/SwapHelper.sol @@ -51,7 +51,9 @@ contract SwapHelper is TestHelper { return beforeSwapFrom(act); } - function beforeSwapFrom(SwapAction memory act) internal returns (Snapshot memory, SwapAction memory) { + function beforeSwapFrom( + SwapAction memory act + ) internal returns (Snapshot memory, SwapAction memory) { Snapshot memory bef = _newSnapshot(); vm.expectEmit(true, true, true, true, address(well)); diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 3289be13..9190b2ed 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -71,7 +71,9 @@ abstract contract TestHelper is Test, WellDeployer { // Initial liquidity amount given to users and wells uint256 public constant initialLiquidity = 1000 * 1e18; - function setupWell(uint256 n) internal { + function setupWell( + uint256 n + ) internal { setupWell(n, deployWellFunction(), deployPumps(1)); } @@ -110,7 +112,9 @@ abstract contract TestHelper is Test, WellDeployer { addLiquidityEqualAmount(address(this), initialLiquidity); } - function setupWellWithFeeOnTransfer(uint256 n) internal { + function setupWellWithFeeOnTransfer( + uint256 n + ) internal { Call memory _wellFunction = Call(address(new ConstantProduct2()), new bytes(0)); Call[] memory _pumps = new Call[](2); _pumps[0].target = address(new MockPump()); @@ -170,14 +174,18 @@ abstract contract TestHelper is Test, WellDeployer { //////////// Test Tokens //////////// /// @dev deploy `n` mock ERC20 tokens and sort by address - function deployMockTokens(uint256 n) internal returns (IERC20[] memory _tokens) { + function deployMockTokens( + uint256 n + ) internal returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; i++) { _tokens[i] = deployMockToken(i); } } - function deployMockToken(uint256 i) internal returns (IERC20) { + function deployMockToken( + uint256 i + ) internal returns (IERC20) { return IERC20( new MockToken( string.concat("Token ", i.toString()), // name @@ -198,14 +206,18 @@ abstract contract TestHelper is Test, WellDeployer { } /// @dev deploy `n` mock ERC20 tokens and sort by address - function deployMockTokensFeeOnTransfer(uint256 n) internal returns (IERC20[] memory _tokens) { + function deployMockTokensFeeOnTransfer( + uint256 n + ) internal returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; i++) { _tokens[i] = deployMockTokenFeeOnTransfer(i); } } - function deployMockTokenFeeOnTransfer(uint256 i) internal returns (IERC20) { + function deployMockTokenFeeOnTransfer( + uint256 i + ) internal returns (IERC20) { return IERC20( new MockTokenFeeOnTransfer( string.concat("Token ", i.toString()), // name @@ -237,7 +249,9 @@ abstract contract TestHelper is Test, WellDeployer { } /// @dev gets the first `n` mock tokens - function getTokens(uint256 n) internal view returns (IERC20[] memory _tokens) { + function getTokens( + uint256 n + ) internal view returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; ++i) { _tokens[i] = tokens[i]; @@ -251,7 +265,9 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = new bytes(0); } - function deployWellFunction(address _target) internal pure returns (Call memory _wellFunction) { + function deployWellFunction( + address _target + ) internal pure returns (Call memory _wellFunction) { _wellFunction.target = _target; _wellFunction.data = new bytes(0); } @@ -264,7 +280,9 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = _data; } - function deployPumps(uint256 n) internal returns (Call[] memory _pumps) { + function deployPumps( + uint256 n + ) internal returns (Call[] memory _pumps) { _pumps = new Call[](n); for (uint256 i; i < n; i++) { _pumps[i].target = address(new MockPump()); @@ -307,11 +325,15 @@ abstract contract TestHelper is Test, WellDeployer { //////////// EVM Helpers //////////// - function increaseTime(uint256 _seconds) internal { + function increaseTime( + uint256 _seconds + ) internal { vm.warp(block.timestamp + _seconds); } - modifier prank(address from) { + modifier prank( + address from + ) { vm.startPrank(from); _; vm.stopPrank(); @@ -436,7 +458,9 @@ abstract contract TestHelper is Test, WellDeployer { snapshot.reserves = well.getReserves(); } - function checkInvariant(address _well) internal view { + function checkInvariant( + address _well + ) internal view { uint256[] memory _reserves = IWell(_well).getReserves(); Call memory _wellFunction = IWell(_well).wellFunction(); assertLe( @@ -446,7 +470,9 @@ abstract contract TestHelper is Test, WellDeployer { ); } - function checkStableSwapInvariant(address _well) internal view { + function checkStableSwapInvariant( + address _well + ) internal view { uint256[] memory _reserves = IWell(_well).getReserves(); Call memory _wellFunction = IWell(_well).wellFunction(); assertApproxEqAbs( @@ -456,7 +482,9 @@ abstract contract TestHelper is Test, WellDeployer { ); } - function getPrecisionForReserves(uint256[] memory reserves) internal pure returns (uint256 precision) { + function getPrecisionForReserves( + uint256[] memory reserves + ) internal pure returns (uint256 precision) { precision = type(uint256).max; for (uint256 i; i < reserves.length; ++i) { uint256 logReserve = reserves[i].log10(); @@ -464,14 +492,18 @@ abstract contract TestHelper is Test, WellDeployer { } } - function uint2ToUintN(uint256[2] memory input) internal pure returns (uint256[] memory out) { + function uint2ToUintN( + uint256[2] memory input + ) internal pure returns (uint256[] memory out) { out = new uint256[](input.length); for (uint256 i; i < input.length; i++) { out[i] = input[i]; } } - function numDigits(uint256 number) internal pure returns (uint256 digits) { + function numDigits( + uint256 number + ) internal pure returns (uint256 digits) { while (number > 9) { number /= 10; digits++; diff --git a/test/Well.RemoveLiquidity.t.sol b/test/Well.RemoveLiquidity.t.sol index 66fd7369..39c6baef 100644 --- a/test/Well.RemoveLiquidity.t.sol +++ b/test/Well.RemoveLiquidity.t.sol @@ -79,7 +79,9 @@ contract WellRemoveLiquidityTest is LiquidityHelper { /// @dev Fuzz test: EQUAL token reserves, BALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { + function test_removeLiquidity_fuzz( + uint256 a0 + ) public prank(user) { // Setup amounts of liquidity to remove // NOTE: amounts may or may not match the maximum removable by `user`. uint256[] memory amounts = new uint256[](2); diff --git a/test/Well.RemoveLiquidityOneToken.t.sol b/test/Well.RemoveLiquidityOneToken.t.sol index cde4d54e..f740d415 100644 --- a/test/Well.RemoveLiquidityOneToken.t.sol +++ b/test/Well.RemoveLiquidityOneToken.t.sol @@ -73,7 +73,9 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { + function testFuzz_removeLiquidityOneToken( + uint256 a0 + ) public prank(user) { // Assume we're removing tokens[0] uint256[] memory amounts = new uint256[](2); amounts[0] = bound(a0, 1e6, 750e18); diff --git a/test/Well.Shift.t.sol b/test/Well.Shift.t.sol index ad7d9c8f..d301d7a8 100644 --- a/test/Well.Shift.t.sol +++ b/test/Well.Shift.t.sol @@ -16,7 +16,9 @@ contract WellShiftTest is TestHelper { } /// @dev Shift excess token0 into token1. - function testFuzz_shift(uint256 amount) public prank(user) { + function testFuzz_shift( + uint256 amount + ) public prank(user) { amount = bound(amount, 1, 1000e18); // Transfer `amount` of token0 to the Well @@ -70,7 +72,9 @@ contract WellShiftTest is TestHelper { } /// @dev Shift excess token0 into token0 (just transfers the excess token0 to the user). - function testFuzz_shift_tokenOut(uint256 amount) public prank(user) { + function testFuzz_shift_tokenOut( + uint256 amount + ) public prank(user) { amount = bound(amount, 1, 1000e18); // Transfer `amount` of token0 to the Well @@ -147,7 +151,9 @@ contract WellShiftTest is TestHelper { checkInvariant(address(well)); } - function test_shift_fail_slippage(uint256 amount) public prank(user) { + function test_shift_fail_slippage( + uint256 amount + ) public prank(user) { amount = bound(amount, 1, 1000e18); // Transfer `amount` of token0 to the Well diff --git a/test/Well.Skim.t.sol b/test/Well.Skim.t.sol index 4c6d32f8..86339eab 100644 --- a/test/Well.Skim.t.sol +++ b/test/Well.Skim.t.sol @@ -15,7 +15,9 @@ contract WellSkimStableSwapTest is TestHelper { assertEq(wellBalance.tokens[1], 1000e18); } - function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { + function testFuzz_skim( + uint256[2] calldata amounts + ) public prank(user) { vm.assume(amounts[0] <= 800e18); vm.assume(amounts[1] <= 800e18); diff --git a/test/Well.SwapFrom.t.sol b/test/Well.SwapFrom.t.sol index 190d3c79..fc1844d6 100644 --- a/test/Well.SwapFrom.t.sol +++ b/test/Well.SwapFrom.t.sol @@ -69,7 +69,9 @@ contract WellSwapFromTest is SwapHelper { well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); } - function testFuzz_swapFrom(uint256 amountIn) public prank(user) { + function testFuzz_swapFrom( + uint256 amountIn + ) public prank(user) { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); @@ -79,7 +81,9 @@ contract WellSwapFromTest is SwapHelper { } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapFrom_equalSwap(uint256 token0AmtIn) public prank(user) { + function testFuzz_swapFrom_equalSwap( + uint256 token0AmtIn + ) public prank(user) { token0AmtIn = bound(token0AmtIn, 0, tokens[0].balanceOf(user)); uint256 token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint256).max); uint256 token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint256).max); diff --git a/test/Well.SwapFromFeeOnTransfer.Fee.t.sol b/test/Well.SwapFromFeeOnTransfer.Fee.t.sol index 9aa40f90..e11b8888 100644 --- a/test/Well.SwapFromFeeOnTransfer.Fee.t.sol +++ b/test/Well.SwapFromFeeOnTransfer.Fee.t.sol @@ -45,7 +45,9 @@ contract WellSwapFromFeeOnTransferFeeTest is SwapHelper { * Well sends: amountOut token1 * User receives: amountOut token1 */ - function testFuzz_swapFromFeeOnTransferWithFee_fromToken(uint256 amountIn) public prank(user) { + function testFuzz_swapFromFeeOnTransferWithFee_fromToken( + uint256 amountIn + ) public prank(user) { amountIn = bound(amountIn, 0, tokens[0].balanceOf(address(user))); Snapshot memory bef; SwapAction memory act; @@ -83,7 +85,9 @@ contract WellSwapFromFeeOnTransferFeeTest is SwapHelper { * the Swap event contains the amount sent by the Well, which will be larger * than the amount received by the User. */ - function testFuzz_swapFromFeeOnTransferWithFee_toToken(uint256 amountIn) public prank(user) { + function testFuzz_swapFromFeeOnTransferWithFee_toToken( + uint256 amountIn + ) public prank(user) { amountIn = bound(amountIn, 0, tokens[1].balanceOf(address(well))); Snapshot memory bef; SwapAction memory act; @@ -107,7 +111,9 @@ contract WellSwapFromFeeOnTransferFeeTest is SwapHelper { afterSwapFrom(bef, act); } - function _getFee(uint256 amount) internal view returns (uint256) { + function _getFee( + uint256 amount + ) internal view returns (uint256) { return amount * MockTokenFeeOnTransfer(address(tokens[0])).fee() / 1e18; } } diff --git a/test/Well.SwapFromFeeOnTransfer.NoFee.t.sol b/test/Well.SwapFromFeeOnTransfer.NoFee.t.sol index eedcd056..7fadcba6 100644 --- a/test/Well.SwapFromFeeOnTransfer.NoFee.t.sol +++ b/test/Well.SwapFromFeeOnTransfer.NoFee.t.sol @@ -28,7 +28,9 @@ contract WellSwapFromFeeOnTransferNoFeeTest is SwapHelper { } /// @dev Swaps should always revert if `fromToken` = `toToken`. - function testFuzz_swapFromFeeOnTransferNoFee_revertIf_sameToken(uint128 amountIn) public prank(user) { + function testFuzz_swapFromFeeOnTransferNoFee_revertIf_sameToken( + uint128 amountIn + ) public prank(user) { MockToken(address(tokens[0])).mint(user, amountIn); vm.expectRevert(IWellErrors.InvalidTokens.selector); @@ -42,7 +44,9 @@ contract WellSwapFromFeeOnTransferNoFeeTest is SwapHelper { } /// @dev With no fees, behavior is identical to {swapFrom}. - function testFuzz_swapFromFeeOnTransfer_noFee(uint256 amountIn) public prank(user) { + function testFuzz_swapFromFeeOnTransfer_noFee( + uint256 amountIn + ) public prank(user) { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); diff --git a/test/Well.SwapTo.t.sol b/test/Well.SwapTo.t.sol index eec8f30a..0cf6f7d9 100644 --- a/test/Well.SwapTo.t.sol +++ b/test/Well.SwapTo.t.sol @@ -19,7 +19,9 @@ contract WellSwapToTest is SwapHelper { assertEq(amountIn, 1000 * 1e18); } - function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { + function testFuzz_getSwapIn_revertIf_insufficientWellBalance( + uint256 i + ) public prank(user) { IERC20[] memory _tokens = well.tokens(); Balances memory wellBalances = getBalances(address(well), well); i = bound(i, 0, _tokens.length); @@ -59,7 +61,9 @@ contract WellSwapToTest is SwapHelper { } /// @dev tests assume 2 tokens in future we can extend for multiple tokens - function testFuzz_swapTo(uint256 amountOut) public prank(user) { + function testFuzz_swapTo( + uint256 amountOut + ) public prank(user) { // User has 1000 of each token // Given current liquidity, swapping 1000 of one token gives 500 of the other uint256 maxAmountIn = 1000 * 1e18; @@ -99,7 +103,9 @@ contract WellSwapToTest is SwapHelper { } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { + function testFuzz_swapTo_equalSwap( + uint256 token0AmtOut + ) public prank(user) { // assume amtOut is lower due to slippage vm.assume(token0AmtOut < 500e18); uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); diff --git a/test/Well.Tokens.t.sol b/test/Well.Tokens.t.sol index bea8cfcd..b731505e 100644 --- a/test/Well.Tokens.t.sol +++ b/test/Well.Tokens.t.sol @@ -33,7 +33,9 @@ contract WellInternalTest is Well, Test { assertEq(j, 0); } - function testFuzz_getIJ(uint256 n) public { + function testFuzz_getIJ( + uint256 n + ) public { n = bound(n, 2, 16); _tokens = new IERC20[](n); diff --git a/test/beanstalk/BeanstalkConstantProduct2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkConstantProduct2.calcReserveAtRatioLiquidity.t.sol index b590064c..79e10e52 100644 --- a/test/beanstalk/BeanstalkConstantProduct2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkConstantProduct2.calcReserveAtRatioLiquidity.t.sol @@ -120,4 +120,16 @@ contract BeanstalkConstantProduct2LiquidityTest is TestHelper { vm.expectRevert(); _f.calcReserveAtRatioLiquidity(reserves, 2, ratios, ""); } + + function test_calcReserveAtRatioLiquidity_infinite() public view { + uint256[] memory reserves = new uint256[](2); + reserves[0] = type(uint256).max; + reserves[1] = type(uint256).max; + uint256[] memory ratios = new uint256[](2); + ratios[0] = type(uint256).max; + ratios[1] = type(uint256).max; + + uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, ""); + assertEq(reserve0, type(uint256).max); + } } diff --git a/test/functions/ConstantProduct.t.sol b/test/functions/ConstantProduct.t.sol index b2635af7..9359211d 100644 --- a/test/functions/ConstantProduct.t.sol +++ b/test/functions/ConstantProduct.t.sol @@ -18,7 +18,9 @@ contract ConstantProductTest is WellFunctionHelper { //////////// LP TOKEN SUPPLY //////////// /// @dev calcLpTokenSupply: `n` equal reserves should summate with the token supply - function testLpTokenSupplySmall(uint256 n) public { + function testLpTokenSupplySmall( + uint256 n + ) public { n = bound(n, 2, 15); uint256[] memory reserves = new uint256[](n); for (uint256 i; i < n; ++i) { diff --git a/test/functions/ConstantProduct2.t.sol b/test/functions/ConstantProduct2.t.sol index b4df4429..e5645b93 100644 --- a/test/functions/ConstantProduct2.t.sol +++ b/test/functions/ConstantProduct2.t.sol @@ -123,7 +123,9 @@ contract ConstantProduct2Test is WellFunctionHelper { //////////// LP TOKEN SUPPLY //////////// /// @dev invariant: reserves -> lpTokenSupply -> reserves should match - function testFuzz_calcLpTokenSupply(uint256[2] memory _reserves) public { + function testFuzz_calcLpTokenSupply( + uint256[2] memory _reserves + ) public { uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 1, MAX_RESERVE); reserves[1] = bound(_reserves[1], 1, MAX_RESERVE); @@ -173,7 +175,9 @@ contract ConstantProduct2Test is WellFunctionHelper { assertEq(_function.calcRate(reserves, 1, 0, _data), 0.01e18); } - function test_fuzz_calcRate(uint256[2] memory _reserves) public { + function test_fuzz_calcRate( + uint256[2] memory _reserves + ) public { uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 1, MAX_RESERVE); reserves[1] = bound(_reserves[1], 1, MAX_RESERVE); @@ -181,4 +185,11 @@ contract ConstantProduct2Test is WellFunctionHelper { assertEq(_function.calcRate(reserves, 1, 0, _data), reserves[1] * 1e18 / reserves[0]); } + + function test_calcRate_infinite() public view { + uint256[] memory reserves = new uint256[](2); + reserves[0] = type(uint256).max; + reserves[1] = type(uint256).max; + assertEq(_function.calcRate(reserves, 0, 1, _data), _function.ratioPrecision(0, _data)); + } } diff --git a/test/functions/Stable2.t.sol b/test/functions/Stable2.t.sol index eeb9c731..29da4678 100644 --- a/test/functions/Stable2.t.sol +++ b/test/functions/Stable2.t.sol @@ -122,7 +122,9 @@ contract Stable2Test is WellFunctionHelper { //////////// LP TOKEN SUPPLY //////////// /// @dev invariant: reserves -> lpTokenSupply -> reserves should match - function testFuzz_calcLpTokenSupply(uint256[2] memory _reserves) public { + function testFuzz_calcLpTokenSupply( + uint256[2] memory _reserves + ) public { _data = abi.encode(18, 18); uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 10e18, MAX_RESERVE); diff --git a/test/functions/WellFunctionHelper.sol b/test/functions/WellFunctionHelper.sol index d79c0341..9c1c0b3f 100644 --- a/test/functions/WellFunctionHelper.sol +++ b/test/functions/WellFunctionHelper.sol @@ -12,14 +12,18 @@ abstract contract WellFunctionHelper is TestHelper { /// @dev calcLpTokenSupply: 0 reserves = 0 supply /// Some Well Functions will choose to support > 2 tokens. /// Additional tokens passed in `reserves` should be ignored. - function test_calcLpTokenSupply_empty(uint256 n) public { + function test_calcLpTokenSupply_empty( + uint256 n + ) public { n = bound(n, 2, 15); uint256[] memory reserves = new uint256[](n); assertEq(_function.calcLpTokenSupply(reserves, _data), 0); } /// @dev require at least `n` reserves to be passed to `calcLpTokenSupply` - function check_calcLpTokenSupply_minBalancesLength(uint256 n) public { + function check_calcLpTokenSupply_minBalancesLength( + uint256 n + ) public { for (uint256 i; i < n; ++i) { vm.expectRevert(stdError.indexOOBError); // "Index out of bounds" _function.calcLpTokenSupply(new uint256[](i), _data); diff --git a/test/helpers/Users.sol b/test/helpers/Users.sol index 7783f97f..5f586ca4 100644 --- a/test/helpers/Users.sol +++ b/test/helpers/Users.sol @@ -17,7 +17,9 @@ contract Users is Test { } //create users with 100 ether balance - function createUsers(uint256 userNum) external returns (address[] memory) { + function createUsers( + uint256 userNum + ) external returns (address[] memory) { address[] memory users = new address[](userNum); for (uint256 i; i < userNum; i++) { address user = this.getNextUserAddress(); @@ -27,7 +29,9 @@ contract Users is Test { } //move block.number forward by a given number of blocks - function mineBlocks(uint256 numBlocks) external { + function mineBlocks( + uint256 numBlocks + ) external { uint256 targetBlock = block.number + numBlocks; vm.roll(targetBlock); } diff --git a/test/integration/IntegrationTestGasComparisons.sol b/test/integration/IntegrationTestGasComparisons.sol index bc690100..b02a1d2a 100644 --- a/test/integration/IntegrationTestGasComparisons.sol +++ b/test/integration/IntegrationTestGasComparisons.sol @@ -65,7 +65,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { ////////// Wells - function testFuzz_wells_WethDai_Swap(uint256 amountIn) public { + function testFuzz_wells_WethDai_Swap( + uint256 amountIn + ) public { vm.pauseGasMetering(); amountIn = bound(amountIn, 1e18, daiWethTokens[1].balanceOf(address(this))); vm.resumeGasMetering(); @@ -73,7 +75,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { daiWethWell.swapFrom(daiWethTokens[1], daiWethTokens[0], amountIn, 0, address(this), type(uint256).max); } - function testFuzz_wells_WethDaiUsdc_Swap(uint256 amountIn) public { + function testFuzz_wells_WethDaiUsdc_Swap( + uint256 amountIn + ) public { vm.pauseGasMetering(); amountIn = bound(amountIn, 1e18, 1000e18); @@ -125,7 +129,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { depot.farm(_farmCalls); } - function testFuzz_wells_WethDaiUsdc_Shift(uint256 amountIn) public { + function testFuzz_wells_WethDaiUsdc_Shift( + uint256 amountIn + ) public { vm.pauseGasMetering(); amountIn = bound(amountIn, 1e18, 1000e18); @@ -157,7 +163,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { depot.farm(_farmCalls); } - function testFuzz_wells_WethDai_AddLiquidity(uint256 amount) public { + function testFuzz_wells_WethDai_AddLiquidity( + uint256 amount + ) public { vm.pauseGasMetering(); uint256[] memory amounts = new uint256[](2); amounts[0] = bound(amount, 1e18, 1000e18); @@ -167,7 +175,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { daiWethWell.addLiquidity(amounts, 0, address(this), type(uint256).max); } - function testFuzz_wells_WethDai_RemoveLiquidity(uint256 amount) public { + function testFuzz_wells_WethDai_RemoveLiquidity( + uint256 amount + ) public { vm.pauseGasMetering(); uint256[] memory amounts = new uint256[](2); amounts[0] = bound(amount, 1e18, 1000e18); @@ -188,7 +198,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { ////////// Uniswap V2 - function testFuzz_uniswapV2_WethDai_Swap(uint256 amount) public { + function testFuzz_uniswapV2_WethDai_Swap( + uint256 amount + ) public { vm.pauseGasMetering(); vm.assume(amount > 0); amount = bound(amount, 1e18, 1000 * 1e18); @@ -203,7 +215,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { uniV2Router.swapExactTokensForTokens(amount, 0, path, msg.sender, block.timestamp); } - function testFuzz_uniswapV2_WethDaiUsdc_Swap(uint256 amount) public { + function testFuzz_uniswapV2_WethDaiUsdc_Swap( + uint256 amount + ) public { vm.pauseGasMetering(); vm.assume(amount > 0); amount = bound(amount, 1e18, 1000 * 1e18); @@ -219,7 +233,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { uniV2Router.swapExactTokensForTokens(amount, 0, path, msg.sender, block.timestamp); } - function testFuzz_uniswapV2_WethDai_AddLiquidity(uint256 amount) public { + function testFuzz_uniswapV2_WethDai_AddLiquidity( + uint256 amount + ) public { vm.pauseGasMetering(); amount = bound(amount, 1e18, 1000 * 1e18); _uniSetupHelper(amount, address(uniV2Router)); @@ -228,7 +244,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { uniV2Router.addLiquidity(address(WETH), address(DAI), amount, amount, 1, 1, address(this), block.timestamp); } - function testFuzz_uniswapV2_WethDai_RemoveLiquidity(uint256 amount) public { + function testFuzz_uniswapV2_WethDai_RemoveLiquidity( + uint256 amount + ) public { vm.pauseGasMetering(); amount = bound(amount, 1e18, 1000 * 1e18); _uniSetupHelper(amount, address(uniV2Router)); @@ -245,7 +263,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { ////////// Uniswap V3 - function testFuzz_uniswapV3_WethDai_Swap(uint256 amount) public { + function testFuzz_uniswapV3_WethDai_Swap( + uint256 amount + ) public { vm.pauseGasMetering(); amount = bound(amount, 1e18, 1000 * 1e18); _uniSetupHelper(amount, address(uniV3Router)); @@ -264,7 +284,9 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { uniV3Router.exactInputSingle(params); } - function testFuzz_uniswapV3_WethDaiUsdc_Swap(uint256 amount) public { + function testFuzz_uniswapV3_WethDaiUsdc_Swap( + uint256 amount + ) public { vm.pauseGasMetering(); amount = bound(amount, 1e18, 1000 * 1e18); _uniSetupHelper(amount, address(uniV3Router)); @@ -308,5 +330,7 @@ contract IntegrationTestGasComparisons is IntegrationTestHelper { interface IWETH is IERC20 { function deposit() external payable; - function withdraw(uint256 amount) external; + function withdraw( + uint256 amount + ) external; } diff --git a/test/integration/interfaces/ICurve.sol b/test/integration/interfaces/ICurve.sol index ef0850b6..988b7878 100644 --- a/test/integration/interfaces/ICurve.sol +++ b/test/integration/interfaces/ICurve.sol @@ -17,11 +17,15 @@ interface ICurvePool { uint256 min_amount ) external returns (uint256); - function balances(int128 i) external view returns (uint256); + function balances( + int128 i + ) external view returns (uint256); function fee() external view returns (uint256); - function coins(uint256 i) external view returns (address); + function coins( + uint256 i + ) external view returns (address); function get_virtual_price() external view returns (uint256); @@ -134,13 +138,19 @@ interface I3Curve { } interface ICurveFactory { - function get_coins(address _pool) external view returns (address[4] calldata); + function get_coins( + address _pool + ) external view returns (address[4] calldata); - function get_underlying_coins(address _pool) external view returns (address[8] calldata); + function get_underlying_coins( + address _pool + ) external view returns (address[8] calldata); } interface ICurveCryptoFactory { - function get_coins(address _pool) external view returns (address[8] calldata); + function get_coins( + address _pool + ) external view returns (address[8] calldata); } interface ICurvePoolC { diff --git a/test/integration/interfaces/IPipeline.sol b/test/integration/interfaces/IPipeline.sol index 41d67ac6..af71ee2b 100644 --- a/test/integration/interfaces/IPipeline.sol +++ b/test/integration/interfaces/IPipeline.sol @@ -33,11 +33,17 @@ struct AdvancedPipeCall { } interface IPipeline { - function pipe(PipeCall calldata p) external payable returns (bytes memory result); + function pipe( + PipeCall calldata p + ) external payable returns (bytes memory result); - function multiPipe(PipeCall[] calldata pipes) external payable returns (bytes[] memory results); + function multiPipe( + PipeCall[] calldata pipes + ) external payable returns (bytes[] memory results); - function advancedPipe(AdvancedPipeCall[] calldata pipes) external payable returns (bytes[] memory results); + function advancedPipe( + AdvancedPipeCall[] calldata pipes + ) external payable returns (bytes[] memory results); } interface IDepot { @@ -46,7 +52,9 @@ interface IDepot { uint256 value ) external payable returns (bytes[] memory results); - function farm(bytes[] calldata data) external payable returns (bytes[] memory results); + function farm( + bytes[] calldata data + ) external payable returns (bytes[] memory results); function transferToken( IERC20 token, diff --git a/test/integration/interfaces/IUniswap.sol b/test/integration/interfaces/IUniswap.sol index 77b0b709..172dc594 100644 --- a/test/integration/interfaces/IUniswap.sol +++ b/test/integration/interfaces/IUniswap.sol @@ -55,7 +55,9 @@ interface IUniswapV3Router { /// @notice Swaps amountIn of one token for as much as possible of another token /// @param params The parameters necessary for the swap, encoded as ExactInputSingleParams in calldata /// @return amountOut The amount of the received token - function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); + function exactInputSingle( + ExactInputSingleParams calldata params + ) external payable returns (uint256 amountOut); struct ExactInputParams { bytes path; @@ -68,7 +70,9 @@ interface IUniswapV3Router { /// @notice Swaps amountIn of one token for as much as possible of another along the specified path /// @param params The parameters necessary for the multi-hop swap, encoded as ExactInputParams in calldata /// @return amountOut The amount of the received token - function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); + function exactInput( + ExactInputParams calldata params + ) external payable returns (uint256 amountOut); } interface IUniswapV2Factory { diff --git a/test/invariant/Handler.t.sol b/test/invariant/Handler.t.sol index 6bcc7978..d17b5567 100644 --- a/test/invariant/Handler.t.sol +++ b/test/invariant/Handler.t.sol @@ -59,7 +59,9 @@ contract Handler is Test { /// @dev The number of reverts on callling `shift` uint256 internal s_shiftFails; - constructor(Well well) { + constructor( + Well well + ) { s_LPs.add(msg.sender); // TestHelper adds initial liquidity s_well = well; } @@ -160,7 +162,9 @@ contract Handler is Test { } /// @dev shift - function shift(uint256 addressSeed) public { + function shift( + uint256 addressSeed + ) public { console.log("----------------------------------"); console.log("Shift"); // bound address seed @@ -489,7 +493,9 @@ contract Handler is Test { // helpers /// @dev Convert a seed to an address - function _seedToAddress(uint256 addressSeed) internal view returns (address seedAddress) { + function _seedToAddress( + uint256 addressSeed + ) internal view returns (address seedAddress) { uint160 boundInt = uint160(bound(addressSeed, 1, type(uint160).max)); seedAddress = address(boundInt); if (seedAddress == address(s_well)) { @@ -502,12 +508,16 @@ contract Handler is Test { } /// @dev Convert an index to an existing LP address - function _indexToLpAddress(uint256 addressIndex) internal view returns (address) { + function _indexToLpAddress( + uint256 addressIndex + ) internal view returns (address) { return s_LPs.at(bound(addressIndex, 0, s_LPs.length() - 1)); } /// @dev Convert an index to an existing approvedBy address - function _indexToApprovedByAddress(uint256 addressIndex) internal view returns (address) { + function _indexToApprovedByAddress( + uint256 addressIndex + ) internal view returns (address) { return s_approvedBy.at(bound(addressIndex, 0, s_approvedBy.length() - 1)); } @@ -533,7 +543,9 @@ contract Handler is Test { console.log("Reserve1: %s", reserves[1]); } - function getMaxAddLiquidity(uint256[] memory reserves) internal pure returns (uint256 max) { + function getMaxAddLiquidity( + uint256[] memory reserves + ) internal pure returns (uint256 max) { if (reserves[0] == 0 || reserves[1] == 0) return type(uint96).max; max = type(uint256).max / (reserves[0] * reserves[1] * EXP_PRECISION); if (max > type(uint96).max) max = type(uint96).max; diff --git a/test/libraries/LibMath.t.sol b/test/libraries/LibMath.t.sol index 2ef8086f..32454d59 100644 --- a/test/libraries/LibMath.t.sol +++ b/test/libraries/LibMath.t.sol @@ -27,13 +27,17 @@ contract LibMathTest is TestHelper { assertEq(LibMath.nthRoot(4, 2), LibMath.sqrt(4)); } - function testFuzz_nthRoot_sqrtMatch(uint256 a) public { + function testFuzz_nthRoot_sqrtMatch( + uint256 a + ) public { vm.assume(a < type(uint256).max); assertEq(LibMath.nthRoot(a, 2), LibMath.sqrt(a)); } /// @dev for all even roots, nthRoot exactly matches `n` sqrt iterations - function testFuzz_nthRoot_sqrtMatchAll(uint256 a) public { + function testFuzz_nthRoot_sqrtMatchAll( + uint256 a + ) public { // every even nth root: 2 4 8 16 for (uint256 i = 1; i <= 4; ++i) { uint256 v = a; diff --git a/test/libraries/TestABDK.t.sol b/test/libraries/TestABDK.t.sol index ea741c4d..6a871b3d 100644 --- a/test/libraries/TestABDK.t.sol +++ b/test/libraries/TestABDK.t.sol @@ -17,7 +17,9 @@ contract ABDKTest is TestHelper { /** * @dev no hysteresis: 2^(log2(a)) == a +/- 1 (due to library rounding) */ - function testFuzz_log2Pow2(uint256 a) public { + function testFuzz_log2Pow2( + uint256 a + ) public { a = bound(a, 1, type(uint256).max); uint256 b = (a.fromUInt().log_2()).pow_2().toUInt(); if (a <= 1e18) { @@ -68,12 +70,16 @@ contract ABDKTest is TestHelper { return a.fromUInt().div(b.fromUInt()).powu(c); } - function testFuzz_FromUIntToLog2(uint256 x) public pure { + function testFuzz_FromUIntToLog2( + uint256 x + ) public pure { x = bound(x, 1, type(uint256).max); assertEq(ABDKMathQuad.fromUInt(x).log_2(), ABDKMathQuad.fromUIntToLog2(x)); } - function testFuzz_pow_2ToUInt(uint256 x) public pure { + function testFuzz_pow_2ToUInt( + uint256 x + ) public pure { x = bound(x, 0, 255); // test the pow_2ToUInt function diff --git a/test/pumps/Pump.Update.t.sol b/test/pumps/Pump.Update.t.sol index 90ad784b..1de5961c 100644 --- a/test/pumps/Pump.Update.t.sol +++ b/test/pumps/Pump.Update.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {console, TestHelper} from "test/TestHelper.sol"; -import {MultiFlowPump, ABDKMathQuad} from "src/pumps/MultiFlowPump.sol"; +import {MultiFlowPump, ABDKMathQuad, IMultiFlowPumpWellFunction} from "src/pumps/MultiFlowPump.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; import {from18, to18, mockPumpData} from "test/pumps/PumpHelpers.sol"; import {MockReserveWell} from "mocks/wells/MockReserveWell.sol"; @@ -11,7 +11,7 @@ import {IMultiFlowPumpErrors} from "src/interfaces/pumps/IMultiFlowPumpErrors.so import {log2, powu, UD60x18, wrap, unwrap} from "prb/math/UD60x18.sol"; import {exp2, log2, powu, UD60x18, wrap, unwrap, uUNIT} from "prb/math/UD60x18.sol"; -contract PumpUpdateTest is TestHelper { +contract PumpUpdateTest is TestHelper, MultiFlowPump { using ABDKMathQuad for bytes16; MultiFlowPump pump; @@ -297,4 +297,106 @@ contract PumpUpdateTest is TestHelper { assertApproxEqAbs(cumulativeReserves[0].div(ABDKMathQuad.fromUInt(24)).pow_2().toUInt(), 1_106_681, 1); // = 2^((log2(1632992) * 12 + log2(2000000) * 12) / (12 + 12)) assertApproxEqAbs(cumulativeReserves[1].div(ABDKMathQuad.fromUInt(24)).pow_2().toUInt(), 1_807_203, 1); // = 2^((log2(1224743) * 12 + log2(1000000) * 12) / (12 + 12)) } + + /** + * @notice test that the pump can be deinitialized, when the reserves are 0. + * additionally, test the pump can re-initialize upon the next update. + */ + function test_deinitialize() public prank(user) { + // de initialize the pump by setting reserves to 0 + b[0] = 0; + b[1] = 0; + mWell.update(address(pump), b, data); + mWell.update(address(pump), b, data); + + // verify the reserves cannot be read: + vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); + pump.readLastCappedReserves(address(mWell), data); + + vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); + pump.readLastInstantaneousReserves(address(mWell), data); + + vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); + pump.readLastCumulativeReserves(address(mWell), data); + + vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); + pump.readInstantaneousReserves(address(mWell), data); + + vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); + pump.readCumulativeReserves(address(mWell), data); + + // re-initialize the pump by setting the reserves to non-zero + b[0] = 1e6; + b[1] = 2e6; + mWell.update(address(pump), b, data); + mWell.update(address(pump), b, data); + + uint256[] memory lastReserves = pump.readLastCappedReserves(address(mWell), data); + assertApproxEqAbs(lastReserves[0], 1e6, 1); + assertApproxEqAbs(lastReserves[1], 2e6, 1); + + uint256[] memory lastEmaReserves = pump.readLastInstantaneousReserves(address(mWell), data); + assertApproxEqAbs(lastEmaReserves[0], 1e6, 1); + assertApproxEqAbs(lastEmaReserves[1], 2e6, 1); + + bytes16[] memory lastCumulativeReserves = pump.readLastCumulativeReserves(address(mWell), data); + assertEq(lastCumulativeReserves[0], bytes16(0)); + assertEq(lastCumulativeReserves[1], bytes16(0)); + + uint256[] memory emaReserves = pump.readInstantaneousReserves(address(mWell), data); + assertApproxEqAbs(emaReserves[0], 1e6, 1); + assertApproxEqAbs(emaReserves[1], 2e6, 1); + + bytes16[] memory cumulativeReserves = abi.decode(pump.readCumulativeReserves(address(mWell), data), (bytes16[])); + assertEq(cumulativeReserves[0], bytes16(0)); + assertEq(cumulativeReserves[1], bytes16(0)); + } + + /** + * @notice test the internal `_capRates` function, and verify the reserves return 0. + */ + function test_internal_capRates() public view { + uint256[] memory lastReserves = new uint256[](2); + uint256[] memory reserves = new uint256[](2); + bytes16[][] memory maxRateChanges = new bytes16[][](2); + maxRateChanges[0] = new bytes16[](2); + maxRateChanges[1] = new bytes16[](2); + CapReservesParameters memory capReservesParameters = CapReservesParameters({ + maxLpSupplyIncrease: bytes16(0), + maxLpSupplyDecrease: bytes16(0), + maxRateChanges: maxRateChanges + }); + IMultiFlowPumpWellFunction mfpWf = IMultiFlowPumpWellFunction(wellFunction.target); + + uint256[] memory cappedReserves = _capRates(lastReserves, reserves, 0, capReservesParameters, mfpWf, data); + assertEq(cappedReserves.length, 2); + assertEq(cappedReserves[0], 0); + assertEq(cappedReserves[1], 0); + } + + /** + * @notice test the internal `_capLpTokenSupply` function, and verify the reserves return 0 when + * 'tryCalcLpTokenSupply` fails. + */ + function test_internal_capLpTokenSupply() public view { + uint256[] memory lastReserves = new uint256[](2); + lastReserves[0] = 1e6; + lastReserves[1] = 1e6; + uint256[] memory reserves = new uint256[](2); + bytes16[][] memory maxRateChanges = new bytes16[][](2); + maxRateChanges[0] = new bytes16[](2); + maxRateChanges[1] = new bytes16[](2); + CapReservesParameters memory capReservesParameters = CapReservesParameters({ + maxLpSupplyIncrease: bytes16(0), + maxLpSupplyDecrease: bytes16(0), + maxRateChanges: maxRateChanges + }); + IMultiFlowPumpWellFunction mfpWf = IMultiFlowPumpWellFunction(wellFunction.target); + + uint256[] memory cappedReserves = + _capLpTokenSupply(lastReserves, reserves, 0, capReservesParameters, mfpWf, data, true); + assertEq(cappedReserves.length, 2); + assertEq(cappedReserves[0], 0); + assertEq(cappedReserves[1], 0); + } } diff --git a/test/pumps/PumpHelpers.sol b/test/pumps/PumpHelpers.sol index 24dca9d3..6ab3e279 100644 --- a/test/pumps/PumpHelpers.sol +++ b/test/pumps/PumpHelpers.sol @@ -8,11 +8,15 @@ import {MultiFlowPump} from "src/pumps/MultiFlowPump.sol"; uint256 constant MAX_128 = 2 ** 128; uint256 constant MAX_E18 = 1e18; -function from18(uint256 a) pure returns (bytes16 result) { +function from18( + uint256 a +) pure returns (bytes16 result) { return ABDKMathQuad.from128x128(int256((a * MAX_128) / MAX_E18)); } -function to18(bytes16 a) pure returns (uint256 result) { +function to18( + bytes16 a +) pure returns (uint256 result) { return (uint256(ABDKMathQuad.to128x128(a)) * MAX_E18) / MAX_128; } @@ -60,7 +64,9 @@ function generateRandomUpdate( newSeed = seed; } -function stepSeed(bytes32 seed) pure returns (bytes32 newSeed) { +function stepSeed( + bytes32 seed +) pure returns (bytes32 newSeed) { newSeed = keccak256(abi.encode(seed)); }