From 46342adab59a435c99cea6c9c0f9de28accaf193 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 29 Sep 2024 12:22:43 -0500 Subject: [PATCH] Implement tryCalcRate, implement new precision. --- mocks/functions/MockEmptyFunction.sol | 3 +- mocks/functions/MockFunctionBad.sol | 3 +- mocks/wells/MockStaticWell.sol | 11 +++--- package.json | 2 +- src/functions/ConstantProduct.sol | 23 ++++++++++- src/functions/ConstantProduct2.sol | 19 ++++++++++ src/functions/Stable2.sol | 22 +++++++++-- src/interfaces/IMultiFlowPumpWellFunction.sol | 8 ++++ src/interfaces/IWellFunction.sol | 5 +++ src/pumps/MultiFlowPump.sol | 38 +++++++++++++++---- test/pumps/Pump.CapReserves.t.sol | 2 +- test/pumps/Pump.Fuzz.t.sol | 1 + test/pumps/Pump.Update.t.sol | 1 + 13 files changed, 118 insertions(+), 20 deletions(-) diff --git a/mocks/functions/MockEmptyFunction.sol b/mocks/functions/MockEmptyFunction.sol index 68c49938..600bc8c7 100644 --- a/mocks/functions/MockEmptyFunction.sol +++ b/mocks/functions/MockEmptyFunction.sol @@ -1,7 +1,6 @@ /** * SPDX-License-Identifier: MIT */ - pragma solidity ^0.8.20; import {IWellFunction} from "src/interfaces/IWellFunction.sol"; @@ -27,4 +26,6 @@ contract MockEmptyFunction is IWellFunction { function name() external pure override returns (string memory) {} function symbol() external pure override returns (string memory) {} + + function version() external pure override returns (string memory) {} } diff --git a/mocks/functions/MockFunctionBad.sol b/mocks/functions/MockFunctionBad.sol index d6e50443..624932c6 100644 --- a/mocks/functions/MockFunctionBad.sol +++ b/mocks/functions/MockFunctionBad.sol @@ -1,7 +1,6 @@ /** * SPDX-License-Identifier: MIT */ - pragma solidity ^0.8.20; import {IWellFunction} from "src/interfaces/IWellFunction.sol"; @@ -39,4 +38,6 @@ contract MockFunctionBad is IWellFunction { function name() external pure override returns (string memory) {} function symbol() external pure override returns (string memory) {} + + function version() external pure override returns (string memory) {} } diff --git a/mocks/wells/MockStaticWell.sol b/mocks/wells/MockStaticWell.sol index e9a2b871..8db3ab8a 100644 --- a/mocks/wells/MockStaticWell.sol +++ b/mocks/wells/MockStaticWell.sol @@ -1,7 +1,6 @@ /** * SPDX-License-Identifier: MIT */ - pragma solidity ^0.8.20; import {console} from "test/TestHelper.sol"; @@ -21,7 +20,7 @@ contract MockStaticWell is ReentrancyGuardUpgradeable, ClonePlus { address immutable TOKEN0; address immutable TOKEN1; address immutable WELL_FUNCTION_TARGET; - bytes32 immutable WELL_FUNCTION_DATA; + bytes internal wellFunctionData; address immutable PUMP0_TARGET; bytes32 immutable PUMP0_DATA; address immutable AQUIFER; @@ -43,7 +42,7 @@ contract MockStaticWell is ReentrancyGuardUpgradeable, ClonePlus { TOKEN0 = address(_tokens[0]); TOKEN1 = address(_tokens[1]); WELL_FUNCTION_TARGET = _wellFunction.target; - WELL_FUNCTION_DATA = bytes32(_wellFunction.data); + wellFunctionData = _wellFunction.data; PUMP0_TARGET = _pumps[0].target; PUMP0_DATA = bytes32(_pumps[0].data); AQUIFER = _aquifer; @@ -62,7 +61,7 @@ contract MockStaticWell is ReentrancyGuardUpgradeable, ClonePlus { } function wellFunction() public view returns (Call memory _wellFunction) { - _wellFunction = Call(WELL_FUNCTION_TARGET, bytes32ToBytes(WELL_FUNCTION_DATA)); + _wellFunction = Call(WELL_FUNCTION_TARGET, wellFunctionData); } function pumps() public view returns (Call[] memory _pumps) { @@ -87,7 +86,9 @@ contract MockStaticWell is ReentrancyGuardUpgradeable, ClonePlus { } /// @dev Inefficient way to convert bytes32 back to bytes without padding - function bytes32ToBytes(bytes32 data) internal pure returns (bytes memory) { + function bytes32ToBytes( + bytes32 data + ) internal pure returns (bytes memory) { uint256 i = 0; while (i < 32 && uint8(data[i]) != 0) { ++i; diff --git a/package.json b/package.json index 82da82b7..fcd9b7b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.2.0", + "version": "1.3.0", "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/src/functions/ConstantProduct.sol b/src/functions/ConstantProduct.sol index dcd94b90..9787b651 100644 --- a/src/functions/ConstantProduct.sol +++ b/src/functions/ConstantProduct.sol @@ -55,8 +55,14 @@ contract ConstantProduct is ProportionalLPToken, IBeanstalkWellFunction { return "CP"; } + function version() external pure override returns (string memory) { + return "1.2.0"; + } + /// @dev calculate the mathematical product of an array of uint256[] - function _prodX(uint256[] memory xs) private pure returns (uint256 pX) { + function _prodX( + uint256[] memory xs + ) private pure returns (uint256 pX) { pX = xs[0]; uint256 length = xs.length; for (uint256 i = 1; i < length; ++i) { @@ -103,4 +109,19 @@ contract ConstantProduct is ProportionalLPToken, IBeanstalkWellFunction { ) external pure returns (uint256 rate) { return reserves[i] * CALC_RATE_PRECISION / reserves[j]; } + + /** + * @notice Returns the precision of the ratio at which the pump will cap the reserve at. + * @param j The index of the reserve to solve for + * @param data The data passed to the well function + * @return precision The precision of the ratio at which the pump will cap the reserve at + */ + function ratioPrecision(uint256 j, bytes calldata data) external pure returns (uint256 precision) { + (uint256 iDecimals, uint256 jDecimals) = abi.decode(data, (uint256, uint256)); + if (j == 0) { + return ((10 ** iDecimals) * CALC_RATE_PRECISION) / (10 ** jDecimals); + } else { + return ((10 ** jDecimals) * CALC_RATE_PRECISION) / (10 ** iDecimals); + } + } } diff --git a/src/functions/ConstantProduct2.sol b/src/functions/ConstantProduct2.sol index 30ba6003..75c26980 100644 --- a/src/functions/ConstantProduct2.sol +++ b/src/functions/ConstantProduct2.sol @@ -81,6 +81,10 @@ contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction { return "CP2"; } + function version() external pure override returns (string memory) { + return "1.2.0"; + } + /// @dev `b_j = (b_0 * b_1 * r_j / r_i)^(1/2)` /// Note: Always rounds down function calcReserveAtRatioSwap( @@ -114,4 +118,19 @@ contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction { ) external pure returns (uint256 rate) { return reserves[i] * CALC_RATE_PRECISION / reserves[j]; } + + /** + * @notice Returns the precision of the ratio at which the pump will cap the reserve at. + * @param j The index of the reserve to solve for + * @param data The data passed to the well function + * @return precision The precision of the ratio at which the pump will cap the reserve at + */ + function ratioPrecision(uint256 j, bytes calldata data) external pure returns (uint256 precision) { + (uint256 iDecimals, uint256 jDecimals) = abi.decode(data, (uint256, uint256)); + if (j == 0) { + return ((10 ** iDecimals) * CALC_RATE_PRECISION) / (10 ** jDecimals); + } else { + return ((10 ** jDecimals) * CALC_RATE_PRECISION) / (10 ** iDecimals); + } + } } diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 430683a3..c13c38f7 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -58,7 +58,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // 2. getRatiosFromPriceSwap(uint256) -> PriceData memory // 3. getAParameter() -> uint256 // Lookup tables are a function of the A parameter. - constructor(address lut) { + constructor( + address lut + ) { if (lut == address(0)) revert InvalidLUT(); lookupTable = lut; a = ILookupTable(lut).getAParameter(); @@ -274,7 +276,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { /** * @inheritdoc IBeanstalkWellFunction * @dev `calcReserveAtRatioLiquidity` fetches the closes approximate ratios from the target price, - * and performs newtons method in order to converge into a reserve. */ function calcReserveAtRatioLiquidity( uint256[] calldata reserves, @@ -354,7 +355,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { * @notice decodes the data encoded in the well. * @return decimals an array of the decimals of the tokens in the well. */ - function decodeWellData(bytes memory data) public view virtual returns (uint256[] memory decimals) { + function decodeWellData( + bytes memory data + ) public view virtual returns (uint256[] memory decimals) { (uint256 decimal0, uint256 decimal1) = abi.decode(data, (uint256, uint256)); // if well data returns 0, assume 18 decimals. @@ -379,6 +382,10 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { return "S2"; } + function version() external pure returns (string memory) { + return "1.1.0"; + } + /** * @notice internal calcRate function. */ @@ -397,6 +404,15 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { rate = _reserves[i] - calcReserve(_reserves, i, lpTokenSupply, abi.encode(18, 18)); } + /** + * @inheritdoc IMultiFlowPumpWellFunction + * @notice Returns the precision of the ratio at which the pump will cap the reserve at. + * @dev {Stable2.calcRate} returns the rate with PRICE_PRECISION, independent of data or index. + */ + function ratioPrecision(uint256, bytes calldata) external pure returns (uint256 precision) { + return PRICE_PRECISION; + } + /** * @notice scale `reserves` by `precision`. * @dev this sets both reserves to 18 decimals. diff --git a/src/interfaces/IMultiFlowPumpWellFunction.sol b/src/interfaces/IMultiFlowPumpWellFunction.sol index 5a067219..81d4610f 100644 --- a/src/interfaces/IMultiFlowPumpWellFunction.sol +++ b/src/interfaces/IMultiFlowPumpWellFunction.sol @@ -42,4 +42,12 @@ interface IMultiFlowPumpWellFunction is IWellFunction { uint256 j, bytes calldata data ) external view returns (uint256 rate); + + /** + * @notice Returns the precision of the ratio at which the pump will cap the reserve at. + * @param j The index of the reserve to solve for + * @param data The data passed to the well function + * @return precision The precision of the ratio at which the pump will cap the reserve at + */ + function ratioPrecision(uint256 j, bytes calldata data) external view returns (uint256); } diff --git a/src/interfaces/IWellFunction.sol b/src/interfaces/IWellFunction.sol index 0742d0d9..1f059371 100644 --- a/src/interfaces/IWellFunction.sol +++ b/src/interfaces/IWellFunction.sol @@ -70,4 +70,9 @@ interface IWellFunction { * @notice Returns the symbol of the Well function. */ function symbol() external view returns (string memory); + + /** + * @notice Returns the version of the Well function. + */ + function version() external view returns (string memory); } diff --git a/src/pumps/MultiFlowPump.sol b/src/pumps/MultiFlowPump.sol index 871647d6..bc8b48e1 100644 --- a/src/pumps/MultiFlowPump.sol +++ b/src/pumps/MultiFlowPump.sol @@ -262,8 +262,8 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu // Use the larger reserve as the numerator for the ratio to maximize precision (uint256 i, uint256 j) = lastReserves[0] > lastReserves[1] ? (0, 1) : (1, 0); CapRatesVariables memory crv; - crv.rLast = mfpWf.calcRate(lastReserves, i, j, data); - crv.r = mfpWf.calcRate(cappedReserves, i, j, data); + crv.rLast = tryCalcRate(mfpWf, lastReserves, i, j, data); + crv.r = tryCalcRate(mfpWf, cappedReserves, i, j, data); // If the ratio increased, check that it didn't increase above the max. if (crv.r > crv.rLast) { @@ -318,7 +318,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 increase below the min. + // If LP Token Suppply 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; @@ -491,7 +491,7 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu ) private view returns (uint256[] memory) { uint256[] memory ratios = new uint256[](2); ratios[i] = rLimit; - ratios[j] = CAP_PRECISION; + ratios[j] = mfpWf.ratioPrecision(j, data); // Use a minimum of 1 for reserve. Geometric means will be set to 0 if a reserve is 0. uint256 cappedReserveI = Math.max(tryCalcReserveAtRatioSwap(mfpWf, reserves, i, ratios, data), 1); reserves[j] = Math.max(tryCalcReserveAtRatioSwap(mfpWf, reserves, j, ratios, data), 1); @@ -502,7 +502,9 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu /** * @dev Convert an `address` into a `bytes32` by zero padding the right 12 bytes. */ - function _getSlotForAddress(address addressValue) internal pure returns (bytes32 _slot) { + function _getSlotForAddress( + address addressValue + ) internal pure returns (bytes32 _slot) { _slot = bytes32(bytes20(addressValue)); // Because right padded, no collision on adjacent } @@ -510,14 +512,18 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu * @dev Get the slot number that contains the `n`th element of an array. * slots are seperated by 32 bytes to allow for future expansion of the Pump (i.e supporting Well with more than 3 tokens). */ - function _getSlotsOffset(uint256 numberOfReserves) internal pure returns (uint256 _slotsOffset) { + function _getSlotsOffset( + uint256 numberOfReserves + ) internal pure returns (uint256 _slotsOffset) { _slotsOffset = ((numberOfReserves - 1) / 2 + 1) << 5; } /** * @dev Get the delta between the current and provided timestamp as a `uint256`. */ - function _getDeltaTimestamp(uint40 lastTimestamp) internal view returns (uint256 _deltaTimestamp) { + function _getDeltaTimestamp( + uint40 lastTimestamp + ) internal view returns (uint256 _deltaTimestamp) { return uint256(uint40(block.timestamp) - lastTimestamp); } @@ -555,6 +561,24 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu } } + /** + * @dev Assumes that if `CalcRate` fails, it fails because of overflow. + * If it fails, returns the maximum possible return value for `CalcRate`. + */ + function tryCalcRate( + IMultiFlowPumpWellFunction wf, + uint256[] memory reserves, + uint256 i, + uint256 j, + bytes memory data + ) internal view returns (uint256 rate) { + try wf.calcRate(reserves, i, j, data) returns (uint256 _rate) { + rate = _rate; + } catch { + rate = type(uint256).max; + } + } + /** * @dev Assumes that if `calcLPTokenUnderlying` fails, it fails because of overflow. * If the call fails, returns the maximum possible return value for `calcLPTokenUnderlying`. diff --git a/test/pumps/Pump.CapReserves.t.sol b/test/pumps/Pump.CapReserves.t.sol index d1586153..42d44e49 100644 --- a/test/pumps/Pump.CapReserves.t.sol +++ b/test/pumps/Pump.CapReserves.t.sol @@ -51,7 +51,7 @@ contract CapBalanceTest is TestHelper, MultiFlowPump { _well = address( new MockStaticWell( - deployMockTokens(2), Call(address(wf), new bytes(0)), deployPumps(1), address(0), new bytes(0) + deployMockTokens(2), Call(address(wf), abi.encode(18, 18)), deployPumps(1), address(0), new bytes(0) ) ); } diff --git a/test/pumps/Pump.Fuzz.t.sol b/test/pumps/Pump.Fuzz.t.sol index 47df7aad..ce0caae5 100644 --- a/test/pumps/Pump.Fuzz.t.sol +++ b/test/pumps/Pump.Fuzz.t.sol @@ -34,6 +34,7 @@ contract PumpFuzzTest is TestHelper, MultiFlowPump { pump = new MultiFlowPump(); data = mockPumpData(); wellFunction.target = address(new ConstantProduct2()); + wellFunction.data = abi.encode(18, 18); mWell.setWellFunction(wellFunction); } diff --git a/test/pumps/Pump.Update.t.sol b/test/pumps/Pump.Update.t.sol index 30a0d6b5..90ad784b 100644 --- a/test/pumps/Pump.Update.t.sol +++ b/test/pumps/Pump.Update.t.sol @@ -28,6 +28,7 @@ contract PumpUpdateTest is TestHelper { pump = new MultiFlowPump(); data = mockPumpData(); wellFunction.target = address(new ConstantProduct2()); + wellFunction.data = abi.encode(18, 18); mWell.setWellFunction(wellFunction); // Send first update to the Pump, which will initialize it