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

Multi Flow Pump v1.1 #127

Merged
merged 38 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0dc2012
first attempt
BrendanSanderson Jan 3, 2024
fee7bc6
switch order of caps
BrendanSanderson Jan 3, 2024
bc90e62
Fixed most tests
BrendanSanderson Jan 18, 2024
691b11a
gas efficiency
BrendanSanderson Jan 22, 2024
72509f2
All Pump tests passing
BrendanSanderson Jan 22, 2024
550e0aa
add Differential testing for Multi Flow Pump
BrendanSanderson Jan 22, 2024
daec681
reduce precision + change to pure
BrendanSanderson Jan 22, 2024
7d9a9ad
fix LibLastReserveBytes
BrendanSanderson Jan 22, 2024
02ec187
fix powu
BrendanSanderson Jan 22, 2024
b328d5b
comment out logs + fix tests
BrendanSanderson Jan 26, 2024
6e52d27
forge update + remove logs + package update
BrendanSanderson Feb 12, 2024
cf26081
add accidentally removed line
BrendanSanderson Feb 24, 2024
4b37735
add constant, remove import console
BrendanSanderson Feb 24, 2024
83d1a07
remove extraneous line + add natspec
BrendanSanderson Feb 24, 2024
a20420a
remove commented out line
BrendanSanderson Feb 24, 2024
3a6ed09
remove TODO
BrendanSanderson Feb 24, 2024
8b439ef
ratio -> rate
BrendanSanderson Feb 24, 2024
e697188
ratio -> rate p2
BrendanSanderson Feb 24, 2024
09ab092
Ratio -> Rate p3
publiuss Feb 24, 2024
b88d7f2
add documentation around cap order
BrendanSanderson Feb 24, 2024
80ed67d
add calcCapExponent
BrendanSanderson Feb 24, 2024
4a2f50e
move struct up
BrendanSanderson Feb 24, 2024
c37e6fc
move enforce min of 1
BrendanSanderson Feb 25, 2024
f46d5ff
mulDiv
BrendanSanderson Feb 25, 2024
c13a263
add calcReservesAtRatioSwap
BrendanSanderson Feb 25, 2024
07b2cd2
Merge pull request #128 from BeanstalkFarms/multi-flow-pump-v1.1-review
Brean0 Feb 25, 2024
43d3e24
M-01 Ignoring the Well Function logic for a ratio of reserves calcula…
pizzaman1337 Jun 17, 2024
001a8c1
WIP fix L-01
pizzaman1337 Jun 17, 2024
ee87734
L-02 Reserves length check in update
pizzaman1337 Jun 18, 2024
9a24526
L-03 fix using bytes number instead of slots number in next slot calc
pizzaman1337 Jun 18, 2024
69b3546
Remove logs
pizzaman1337 Jun 18, 2024
914a0ac
Revert L-03 bytes/slot change
pizzaman1337 Jun 21, 2024
2ef7a10
remove extraneous logs from fuzz tests.
Brean0 Jun 25, 2024
26418bc
remove lines from diff.
Brean0 Jun 25, 2024
9cbc624
Add comment to _getSlotsOffset
Brean0 Jun 30, 2024
a0e49af
Merge pull request #132 from BeanstalkFarms/multi-flow-pump-remediations
Brean0 Jul 1, 2024
8d54b04
bump npm version
BrendanSanderson Jul 8, 2024
badda26
update package.
Brean0 Jul 26, 2024
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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ stash
lcov.info
.DS_Store
test/output/

# Python
__pycache__
11 changes: 11 additions & 0 deletions mocks/wells/MockReserveWell.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,28 @@
pragma solidity ^0.8.20;

import {IPump} from "src/interfaces/pumps/IPump.sol";
import {Call} from "src/interfaces/IWell.sol";

/**
* @notice Mock Well that allows setting of reserves.
*/
contract MockReserveWell {
uint256[] reserves;
Call _wellFunction;


constructor() {
reserves = new uint256[](2);
}

function setWellFunction(Call calldata __wellFunction) external {
_wellFunction = __wellFunction;
}

function wellFunction() external view returns (Call memory) {
return _wellFunction;
}

function setReserves(uint256[] memory _reserves) public {
reserves = _reserves;
}
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": "0.4.0",
"version": "1.1.0-prerelease0",
"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
11 changes: 11 additions & 0 deletions src/functions/ConstantProduct.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {LibMath} from "src/libraries/LibMath.sol";
contract ConstantProduct is ProportionalLPToken, IBeanstalkWellFunction {
using LibMath for uint256;

uint256 constant CALC_RATE_PRECISION = 1e18;

/// @dev `s = π(b_i)^(1/n) * n`
function calcLpTokenSupply(
uint256[] calldata reserves,
Expand Down Expand Up @@ -92,4 +94,13 @@ contract ConstantProduct is ProportionalLPToken, IBeanstalkWellFunction {
}
reserve /= reserves.length - 1;
}

function calcRate(
uint256[] calldata reserves,
uint256 i,
uint256 j,
bytes calldata
) external pure returns (uint256 rate) {
return reserves[i] * CALC_RATE_PRECISION / reserves[j];
}
}
13 changes: 12 additions & 1 deletion src/functions/ConstantProduct2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.20;
import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol";
import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol";
import {LibMath} from "src/libraries/LibMath.sol";
import {Math} from "oz/utils/math/Math.sol";

/**
* @title ConstantProduct2
Expand All @@ -18,9 +19,10 @@ import {LibMath} from "src/libraries/LibMath.sol";
* `b_i` is the reserve at index `i`
*/
contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction {
using LibMath for uint256;
using Math for uint256;

uint256 constant EXP_PRECISION = 1e12;
uint256 constant CALC_RATE_PRECISION = 1e18;

/**
* @dev `s = (b_0 * b_1)^(1/2)`
Expand Down Expand Up @@ -103,4 +105,13 @@ contract ConstantProduct2 is ProportionalLPToken2, IBeanstalkWellFunction {
uint256 i = j == 1 ? 0 : 1;
reserve = reserves[i] * ratios[j] / ratios[i];
}

function calcRate(
uint256[] calldata reserves,
uint256 i,
uint256 j,
bytes calldata
) external pure returns (uint256 rate) {
return reserves[i] * CALC_RATE_PRECISION / reserves[j];
}
}
5 changes: 4 additions & 1 deletion src/functions/ProportionalLPToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.20;

import {IWellFunction} from "src/interfaces/IWellFunction.sol";
import {Math} from "oz/utils/math/Math.sol";

/**
* @title ProportionalLPToken
Expand All @@ -12,6 +13,8 @@ import {IWellFunction} from "src/interfaces/IWellFunction.sol";
* recieves `s * b_i / S` of each underlying token.
*/
abstract contract ProportionalLPToken is IWellFunction {
using Math for uint256;

function calcLPTokenUnderlying(
uint256 lpTokenAmount,
uint256[] calldata reserves,
Expand All @@ -20,7 +23,7 @@ abstract contract ProportionalLPToken is IWellFunction {
) external pure returns (uint256[] memory underlyingAmounts) {
underlyingAmounts = new uint256[](reserves.length);
for (uint256 i; i < reserves.length; ++i) {
underlyingAmounts[i] = lpTokenAmount * reserves[i] / lpTokenSupply;
underlyingAmounts[i] = lpTokenAmount.mulDiv(reserves[i], lpTokenSupply);
}
}
}
7 changes: 5 additions & 2 deletions src/functions/ProportionalLPToken2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.20;

import {IWellFunction} from "src/interfaces/IWellFunction.sol";
import {Math} from "oz/utils/math/Math.sol";

/**
* @title ProportionalLPToken2
Expand All @@ -12,14 +13,16 @@ import {IWellFunction} from "src/interfaces/IWellFunction.sol";
* recieves `s * b_i / S` of each underlying token.
*/
abstract contract ProportionalLPToken2 is IWellFunction {
using Math for uint256;

function calcLPTokenUnderlying(
uint256 lpTokenAmount,
uint256[] calldata reserves,
uint256 lpTokenSupply,
bytes calldata
) external pure returns (uint256[] memory underlyingAmounts) {
underlyingAmounts = new uint256[](2);
underlyingAmounts[0] = lpTokenAmount * reserves[0] / lpTokenSupply;
underlyingAmounts[1] = lpTokenAmount * reserves[1] / lpTokenSupply;
underlyingAmounts[0] = lpTokenAmount.mulDiv(reserves[0], lpTokenSupply);
underlyingAmounts[1] = lpTokenAmount.mulDiv(reserves[1], lpTokenSupply);
}
}
28 changes: 6 additions & 22 deletions src/interfaces/IBeanstalkWellFunction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,18 @@

pragma solidity ^0.8.20;

import {IWellFunction} from "src/interfaces/IWellFunction.sol";
import {IMultiFlowPumpWellFunction} from "src/interfaces/IMultiFlowPumpWellFunction.sol";

/**
* @title IBeanstalkWellFunction
* @notice Defines all necessary functions for Beanstalk to support a Well Function in addition to functions defined in the primary interface.
Brean0 marked this conversation as resolved.
Show resolved Hide resolved
* This includes 2 functions to solve for a given reserve value suc that the average price between
* It extends `IMultiFlowPumpWellFunction` as Beanstalk requires Wells to use MultiFlowPump in order to have access to manipulation resistant oracles.
* Beanstalk requires 2 functions to solve for a given reserve value such that the average price between
* the given reserve and all other reserves equals the average of the input ratios.
* `calcReserveAtRatioSwap` assumes the target ratios are reached through executing a swap.
* `calcReserveAtRatioLiquidity` assumes the target ratios are reached through adding/removing liquidity.
* - `calcReserveAtRatioSwap` assumes the target ratios are reached through executing a swap. Note: `calcReserveAtRatioSwap` is included in {IMultiFlowPumpWellFunction}.
* - `calcReserveAtRatioLiquidity` assumes the target ratios are reached through adding/removing liquidity.
*/
interface IBeanstalkWellFunction is IWellFunction {
/**
* @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`.
* assumes that reserve_j is being swapped for other reserves in the Well.
* @dev used by Beanstalk to calculate the deltaB every Season
* @param reserves The reserves of the Well
* @param j The index of the reserve to solve for
* @param ratios The ratios of reserves to solve for
* @param data Well function data provided on every call
* @return reserve The resulting reserve at the jth index
*/
function calcReserveAtRatioSwap(
uint256[] calldata reserves,
uint256 j,
uint256[] calldata ratios,
bytes calldata data
) external view returns (uint256 reserve);

interface IBeanstalkWellFunction is IMultiFlowPumpWellFunction {
/**
* @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`.
* assumes that reserve_j is being added or removed in exchange for LP Tokens.
Expand Down
45 changes: 45 additions & 0 deletions src/interfaces/IMultiFlowPumpWellFunction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IWellFunction} from "src/interfaces/IWellFunction.sol";

/**
* @title IMultiFlowPumpWellFunction
* @dev A Well Function must implement IMultiFlowPumpWellFunction to be supported by
* the Multi Flow Pump.
*/
interface IMultiFlowPumpWellFunction is IWellFunction {
/**
* @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`.
* assumes that reserve_j is being swapped for other reserves in the Well.
* @dev used by Beanstalk to calculate the deltaB every Season
* @param reserves The reserves of the Well
* @param j The index of the reserve to solve for
* @param ratios The ratios of reserves to solve for
* @param data Well function data provided on every call
* @return reserve The resulting reserve at the jth index
*/
function calcReserveAtRatioSwap(
uint256[] calldata reserves,
uint256 j,
uint256[] calldata ratios,
bytes calldata data
) external view returns (uint256 reserve);

/**
* @notice Calculates the rate at which j can be exchanged for i.
* @param reserves The reserves of the Well
* @param i The index of the token for which the output is being calculated
* @param j The index of the token for which 1 token is being exchanged
* @param data Well function data provided on every call
* @return rate The rate at which j can be exchanged for i
* @dev should return with 36 decimal precision
*/
function calcRate(
uint256[] calldata reserves,
uint256 i,
uint256 j,
bytes calldata data
) external view returns (uint256 rate);
}
2 changes: 2 additions & 0 deletions src/interfaces/pumps/IMultiFlowPumpErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ interface IMultiFlowPumpErrors {
error NotInitialized();

error NoTimePassed();

error TooManyTokens();
}
33 changes: 27 additions & 6 deletions src/libraries/LibLastReserveBytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

pragma solidity ^0.8.20;

import {ABDKMathQuad} from "src/libraries/ABDKMathQuad.sol";

/**
* @title LibLastReserveBytes
* @author Publius
Expand All @@ -15,15 +17,25 @@ pragma solidity ^0.8.20;
* in reserves for manipulation resistance purposes, the gas savings is worth the lose of precision.
*/
library LibLastReserveBytes {
using ABDKMathQuad for uint256;
using ABDKMathQuad for bytes16;

function readNumberOfReserves(bytes32 slot) internal view returns (uint8 _numberOfReserves) {
assembly {
_numberOfReserves := shr(248, sload(slot))
}
}

function storeLastReserves(bytes32 slot, uint40 lastTimestamp, bytes16[] memory reserves) internal {
function storeLastReserves(bytes32 slot, uint40 lastTimestamp, uint256[] memory lastReserves) internal {
// Potential optimization – shift reserve bytes left to perserve extra decimal precision.
uint8 n = uint8(reserves.length);
uint8 n = uint8(lastReserves.length);

bytes16[] memory reserves = new bytes16[](n);

for (uint256 i; i < n; ++i) {
reserves[i] = lastReserves[i].fromUInt();
}

if (n == 1) {
assembly {
sstore(slot, or(or(shl(208, lastTimestamp), shl(248, n)), shl(104, shr(152, mload(add(reserves, 32))))))
Expand Down Expand Up @@ -73,7 +85,7 @@ library LibLastReserveBytes {
function readLastReserves(bytes32 slot)
internal
view
returns (uint8 n, uint40 lastTimestamp, bytes16[] memory reserves)
returns (uint8 n, uint40 lastTimestamp, uint256[] memory lastReserves)
{
// Shortcut: two reserves can be quickly unpacked from one slot
bytes32 temp;
Expand All @@ -82,13 +94,17 @@ library LibLastReserveBytes {
n := shr(248, temp)
lastTimestamp := shr(208, temp)
}
if (n == 0) return (n, lastTimestamp, reserves);
if (n == 0) return (n, lastTimestamp, lastReserves);
// Initialize array with length `n`, fill it in via assembly
reserves = new bytes16[](n);
bytes16[] memory reserves = new bytes16[](n);
assembly {
mstore(add(reserves, 32), shl(152, shr(104, temp)))
}
if (n == 1) return (n, lastTimestamp, reserves);
if (n == 1) {
lastReserves = new uint256[](1);
lastReserves[0] = reserves[0].toUInt();
return (n, lastTimestamp, lastReserves);
}
assembly {
mstore(add(reserves, 64), shl(152, temp))
}
Expand Down Expand Up @@ -116,6 +132,11 @@ library LibLastReserveBytes {
}
}
}

lastReserves = new uint256[](n);
for (uint256 i; i < n; ++i) {
lastReserves[i] = reserves[i].toUInt();
}
}

function readBytes(bytes32 slot) internal view returns (bytes32 value) {
Expand Down
Loading
Loading