Skip to content

Commit

Permalink
Merge pull request #147 from BeanstalkFarms/multi-flow-pump-1.2.1
Browse files Browse the repository at this point in the history
Multi flow pump 1.2.1
  • Loading branch information
nickkatsios authored Oct 17, 2024
2 parents 830ccd8 + d939f82 commit ecf6923
Show file tree
Hide file tree
Showing 45 changed files with 497 additions and 128 deletions.
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.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": {
Expand Down
4 changes: 3 additions & 1 deletion script/deploy/helpers/Logger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
20 changes: 15 additions & 5 deletions src/Well.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}

Expand All @@ -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();
Expand Down Expand Up @@ -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();
}
Expand Down
8 changes: 6 additions & 2 deletions src/WellUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 3 additions & 3 deletions src/functions/ConstantProduct2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand All @@ -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(
Expand All @@ -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]);
}

/**
Expand Down
8 changes: 6 additions & 2 deletions src/functions/StableLUT/Stable2LUT1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/IAquifer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
8 changes: 6 additions & 2 deletions src/interfaces/ILookupTable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
16 changes: 12 additions & 4 deletions src/interfaces/IWell.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 ////////////////////

Expand Down Expand Up @@ -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 ////////////////////

Expand All @@ -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 ////////////////////

Expand Down Expand Up @@ -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.
Expand Down
12 changes: 9 additions & 3 deletions src/libraries/LibContractInfo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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
}
Expand Down
16 changes: 14 additions & 2 deletions src/libraries/LibLastReserveBytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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)
}
Expand Down
4 changes: 3 additions & 1 deletion src/libraries/LibMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 20 additions & 8 deletions src/pumps/MultiFlowPump.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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;
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion test/Stable2/Well.Stable2.RemoveLiquidity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit ecf6923

Please sign in to comment.