Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tryCalcRate, implement new precision. #146

Merged
merged 3 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading