Skip to content

Commit

Permalink
Merge pull request #146 from BeanstalkFarms/multi-flow-pump-v1.2
Browse files Browse the repository at this point in the history
Implement tryCalcRate, implement new precision.
  • Loading branch information
Brean0 authored Oct 6, 2024
2 parents 582a98e + 6859fdd commit 830ccd8
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 22 deletions.
3 changes: 2 additions & 1 deletion mocks/functions/MockEmptyFunction.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* SPDX-License-Identifier: MIT
*/

pragma solidity ^0.8.20;

import {IWellFunction} from "src/interfaces/IWellFunction.sol";
Expand All @@ -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) {}
}
3 changes: 2 additions & 1 deletion mocks/functions/MockFunctionBad.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* SPDX-License-Identifier: MIT
*/

pragma solidity ^0.8.20;

import {IWellFunction} from "src/interfaces/IWellFunction.sol";
Expand Down Expand Up @@ -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) {}
}
11 changes: 6 additions & 5 deletions mocks/wells/MockStaticWell.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* SPDX-License-Identifier: MIT
*/

pragma solidity ^0.8.20;

import {console} from "test/TestHelper.sol";
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
23 changes: 22 additions & 1 deletion src/functions/ConstantProduct.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}
}
11 changes: 11 additions & 0 deletions src/functions/ConstantProduct2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -114,4 +118,11 @@ 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.
*/
function ratioPrecision(uint256, bytes calldata) external pure returns (uint256 precision) {
return CALC_RATE_PRECISION;
}
}
25 changes: 20 additions & 5 deletions src/functions/Stable2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol";

/**
* @author brean, deadmanwalking
* @title Gas efficient StableSwap pricing function for Wells with 2 tokens.
* developed by curve.
* @title Gas efficient Like-valued token pricing function for Wells with 2 tokens.
*
* Stableswap Wells with 2 tokens use the formula:
* `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)`
Expand Down Expand Up @@ -58,7 +57,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();
Expand Down Expand Up @@ -274,7 +275,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,
Expand Down Expand Up @@ -354,7 +354,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.
Expand All @@ -379,6 +381,10 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction {
return "S2";
}

function version() external pure returns (string memory) {
return "1.1.0";
}

/**
* @notice internal calcRate function.
*/
Expand All @@ -397,6 +403,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.
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/IMultiFlowPumpWellFunction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
5 changes: 5 additions & 0 deletions src/interfaces/IWellFunction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
38 changes: 31 additions & 7 deletions src/pumps/MultiFlowPump.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -502,22 +502,28 @@ 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
}

/**
* @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);
}

Expand Down Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion test/pumps/Pump.CapReserves.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
);
}
Expand Down
1 change: 1 addition & 0 deletions test/pumps/Pump.Fuzz.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions test/pumps/Pump.Update.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 830ccd8

Please sign in to comment.