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 1.2.1 #147

Merged
merged 7 commits into from
Oct 17, 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
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
Loading