Contract Name:
BalancerComposableStableAdapter
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {Controllable} from "../core/base/Controllable.sol";
import {ConstantsLib} from "../core/libs/ConstantsLib.sol";
import {AmmAdapterIdLib} from "./libs/AmmAdapterIdLib.sol";
import {Errors} from "./libs/balancer/BalancerErrors.sol";
import {FixedPoint} from "./libs/balancer/FixedPoint.sol";
import {ScaleLib} from "./libs/balancer/ScaleLib.sol";
import {StableMath} from "./libs/balancer/StableMath.sol";
import {IBComposableStablePoolMinimal} from "../integrations/balancer/IBComposableStablePoolMinimal.sol";
import {IBVault, IAsset} from "../integrations/balancer/IBVault.sol";
import {IBalancerHelper, IVault} from "../integrations/balancer/IBalancerHelper.sol";
import {IAmmAdapter} from "../interfaces/IAmmAdapter.sol";
import {IControllable} from "../interfaces/IControllable.sol";
import {IBalancerAdapter} from "../interfaces/IBalancerAdapter.sol";
/// @title AMM adapter for Balancer ComposableStable pools
/// @author Alien Deployer (https://github.com/a17)
contract BalancerComposableStableAdapter is Controllable, IAmmAdapter, IBalancerAdapter {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @inheritdoc IControllable
string public constant VERSION = "1.0.0";
// keccak256(abi.encode(uint256(keccak256("erc7201:stability.BalancerComposableStableAdapter")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant STORAGE_LOCATION = 0x4235c883b69d0c060f4f9a2c87fa015d10166773b6a97be421a79340d62c1e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DATA TYPES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct GetLiquidityForAmountsVars {
bytes32 poolId;
address[] assets;
uint bptIndex;
uint len;
}
struct GetProportionsVars {
uint bptIndex;
uint asset0Index;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @custom:storage-location erc7201:stability.BalancerComposableStableAdapter
struct AdapterStorage {
address balancerHelpers;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INITIALIZATION */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @inheritdoc IAmmAdapter
function init(address platform_) external initializer {
__Controllable_init(platform_);
}
/// @inheritdoc IBalancerAdapter
function setupHelpers(address balancerHelpers) external {
AdapterStorage storage $ = _getStorage();
if ($.balancerHelpers != address(0)) {
revert AlreadyExist();
}
$.balancerHelpers = balancerHelpers;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* USER ACTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @inheritdoc IAmmAdapter
//slither-disable-next-line reentrancy-events
function swap(
address pool,
address tokenIn,
address tokenOut,
address recipient,
uint priceImpactTolerance
) external {
uint amountIn = IERC20(tokenIn).balanceOf(address(this));
address balancerVault = IBComposableStablePoolMinimal(pool).getVault();
// Initializing each struct field one-by-one uses less gas than setting all at once.
IBVault.FundManagement memory funds;
funds.sender = address(this);
funds.fromInternalBalance = false;
funds.recipient = payable(recipient);
funds.toInternalBalance = false;
// Initializing each struct field one-by-one uses less gas than setting all at once.
IBVault.SingleSwap memory singleSwap;
singleSwap.poolId = IBComposableStablePoolMinimal(pool).getPoolId();
singleSwap.kind = IBVault.SwapKind.GIVEN_IN;
singleSwap.assetIn = IAsset(address(tokenIn));
singleSwap.assetOut = IAsset(address(tokenOut));
singleSwap.amount = amountIn;
singleSwap.userData = "";
// scope for checking price impact
uint amountOutMax;
{
uint minimalAmount = amountIn / 1000;
require(minimalAmount != 0, "Too low amountIn");
uint price = getPrice(pool, tokenIn, tokenOut, minimalAmount);
amountOutMax = price * amountIn / minimalAmount;
}
IERC20(tokenIn).approve(balancerVault, amountIn);
uint amountOut = IBVault(balancerVault).swap(singleSwap, funds, 1, block.timestamp);
uint priceImpact =
amountOutMax < amountOut ? 0 : (amountOutMax - amountOut) * ConstantsLib.DENOMINATOR / amountOutMax;
if (priceImpact > priceImpactTolerance) {
revert(string(abi.encodePacked("!PRICE ", Strings.toString(priceImpact))));
}
emit SwapInPool(pool, tokenIn, tokenOut, recipient, priceImpactTolerance, amountIn, amountOut);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* VIEW FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @inheritdoc IAmmAdapter
function ammAdapterId() external pure returns (string memory) {
return AmmAdapterIdLib.BALANCER_COMPOSABLE_STABLE;
}
/// @inheritdoc IAmmAdapter
function poolTokens(address pool) public view returns (address[] memory tokens) {
IBComposableStablePoolMinimal _pool = IBComposableStablePoolMinimal(pool);
(address[] memory bTokens,,) = IBVault(_pool.getVault()).getPoolTokens(_pool.getPoolId());
uint bptIndex = _pool.getBptIndex();
uint len = bTokens.length - 1;
tokens = new address[](len);
for (uint i; i < len; ++i) {
tokens[i] = bTokens[i < bptIndex ? i : i + 1];
}
}
/// @inheritdoc IAmmAdapter
function getLiquidityForAmounts(address, uint[] memory) external pure returns (uint, uint[] memory) {
revert("Unavailable");
}
/// @inheritdoc IBalancerAdapter
function getLiquidityForAmountsWrite(
address pool,
uint[] memory amounts
) external returns (uint liquidity, uint[] memory amountsConsumed) {
GetLiquidityForAmountsVars memory v;
IBComposableStablePoolMinimal _pool = IBComposableStablePoolMinimal(pool);
v.poolId = _pool.getPoolId();
(v.assets,,) = IBVault(_pool.getVault()).getPoolTokens(v.poolId);
v.len = v.assets.length;
v.bptIndex = _pool.getBptIndex();
uint k;
uint[] memory amountsIn;
(liquidity, amountsIn) = IBalancerHelper(_getStorage().balancerHelpers).queryJoin(
v.poolId,
address(this),
address(this),
IVault.JoinPoolRequest({
assets: v.assets,
maxAmountsIn: amounts,
userData: abi.encode(IBVault.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, amounts, 0),
fromInternalBalance: false
})
);
k = 0;
amountsConsumed = new uint[](v.len - 1);
for (uint i; i < v.len; ++i) {
if (i != v.bptIndex) {
amountsConsumed[k] = amountsIn[i];
k++;
}
}
}
/// @inheritdoc IAmmAdapter
function getProportions(address pool) external view returns (uint[] memory props) {
GetProportionsVars memory v;
IBComposableStablePoolMinimal _pool = IBComposableStablePoolMinimal(pool);
v.bptIndex = _pool.getBptIndex();
v.asset0Index = v.bptIndex == 0 ? 1 : 0;
(address[] memory tokens, uint[] memory balances,) = IBVault(_pool.getVault()).getPoolTokens(_pool.getPoolId());
uint totalInAsset0;
uint len = tokens.length;
uint[] memory pricedBalances = new uint[](len - 1);
uint k;
for (uint i; i < len; ++i) {
if (i != v.bptIndex) {
uint tokenDecimals = IERC20Metadata(tokens[i]).decimals();
uint price = i == v.asset0Index
? 10 ** tokenDecimals
: getPrice(pool, address(tokens[i]), address(tokens[v.asset0Index]), 10 ** (tokenDecimals - 3)) * 1000;
pricedBalances[k] = balances[i] * price / 10 ** tokenDecimals;
totalInAsset0 += pricedBalances[k];
k++;
}
}
props = new uint[](len - 1);
for (uint i; i < len - 1; ++i) {
props[i] = pricedBalances[i] * 1e18 / totalInAsset0;
}
}
/// @inheritdoc IAmmAdapter
function getPrice(address pool, address tokenIn, address tokenOut, uint amount) public view returns (uint) {
IBComposableStablePoolMinimal _pool = IBComposableStablePoolMinimal(pool);
{
// take pool commission
uint swapFeePercentage = _pool.getSwapFeePercentage();
amount -= FixedPoint.mulUp(amount, swapFeePercentage);
}
bytes32 poolId = _pool.getPoolId();
(address[] memory tokens, uint[] memory balances,) = IBVault(_pool.getVault()).getPoolTokens(poolId);
uint tokenInIndex = type(uint).max;
uint tokenOutIndex = type(uint).max;
uint len = tokens.length;
for (uint i; i < len; ++i) {
if (tokens[i] == tokenIn) {
tokenInIndex = i;
break;
}
}
for (uint i; i < len; ++i) {
if (tokens[i] == tokenOut) {
tokenOutIndex = i;
break;
}
}
// require(tokenInIndex < len, 'Wrong tokenIn');
// require(tokenOutIndex < len, 'Wrong tokenOut');
uint[] memory scalingFactors = _pool.getScalingFactors();
ScaleLib._upscaleArray(balances, scalingFactors);
uint bptIndex = _pool.getBptIndex();
balances = _dropBptItem(balances, bptIndex);
uint upscaledAmount = ScaleLib._upscale(amount, scalingFactors[tokenInIndex]);
tokenInIndex = _skipBptIndex(tokenInIndex, bptIndex);
uint tokenOutIndexWoBpt = _skipBptIndex(tokenOutIndex, bptIndex);
(uint currentAmp,,) = _pool.getAmplificationParameter();
{
uint invariant = StableMath._calculateInvariant(currentAmp, balances, false);
uint amountOutUpscaled = StableMath._calcOutGivenIn(
currentAmp, balances, tokenInIndex, tokenOutIndexWoBpt, upscaledAmount, invariant
);
return ScaleLib._downscaleDown(amountOutUpscaled, scalingFactors[tokenOutIndex]);
}
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view override(Controllable, IERC165) returns (bool) {
return interfaceId == type(IAmmAdapter).interfaceId || interfaceId == type(IBalancerAdapter).interfaceId
|| super.supportsInterface(interfaceId);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function _dropBptItem(uint[] memory amounts, uint bptIndex) internal pure returns (uint[] memory) {
uint len = amounts.length - 1;
uint[] memory amountsWithoutBpt = new uint[](len);
for (uint i; i < len; ++i) {
amountsWithoutBpt[i] = amounts[i < bptIndex ? i : i + 1];
}
return amountsWithoutBpt;
}
function _skipBptIndex(uint index, uint bptIndex) internal pure returns (uint) {
return index < bptIndex ? index : index - 1;
}
function _getStorage() private pure returns (AdapterStorage storage $) {
//slither-disable-next-line assembly
assembly {
$.slot := STORAGE_LOCATION
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../libs/SlotsLib.sol";
import "../../interfaces/IControllable.sol";
import "../../interfaces/IPlatform.sol";
/// @dev Base core contract.
/// It store an immutable platform proxy address in the storage and provides access control to inherited contracts.
/// @author Alien Deployer (https://github.com/a17)
/// @author 0xhokugava (https://github.com/0xhokugava)
abstract contract Controllable is Initializable, IControllable, ERC165 {
using SlotsLib for bytes32;
string public constant CONTROLLABLE_VERSION = "1.0.0";
bytes32 internal constant _PLATFORM_SLOT = bytes32(uint(keccak256("eip1967.controllable.platform")) - 1);
bytes32 internal constant _CREATED_BLOCK_SLOT = bytes32(uint(keccak256("eip1967.controllable.created_block")) - 1);
/// @dev Prevent implementation init
constructor() {
_disableInitializers();
}
/// @notice Initialize contract after setup it as proxy implementation
/// Save block.timestamp in the "created" variable
/// @dev Use it only once after first logic setup
/// @param platform_ Platform address
//slither-disable-next-line naming-convention
function __Controllable_init(address platform_) internal onlyInitializing {
if (platform_ == address(0) || IPlatform(platform_).multisig() == address(0)) {
revert IncorrectZeroArgument();
}
SlotsLib.set(_PLATFORM_SLOT, platform_); // syntax for forge coverage
_CREATED_BLOCK_SLOT.set(block.number);
emit ContractInitialized(platform_, block.timestamp, block.number);
}
modifier onlyGovernance() {
_requireGovernance();
_;
}
modifier onlyMultisig() {
_requireMultisig();
_;
}
modifier onlyGovernanceOrMultisig() {
_requireGovernanceOrMultisig();
_;
}
modifier onlyOperator() {
_requireOperator();
_;
}
modifier onlyFactory() {
_requireFactory();
_;
}
// ************* SETTERS/GETTERS *******************
/// @inheritdoc IControllable
function platform() public view override returns (address) {
return _PLATFORM_SLOT.getAddress();
}
/// @inheritdoc IControllable
function createdBlock() external view override returns (uint) {
return _CREATED_BLOCK_SLOT.getUint();
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IControllable).interfaceId || super.supportsInterface(interfaceId);
}
function _requireGovernance() internal view {
if (IPlatform(platform()).governance() != msg.sender) {
revert NotGovernance();
}
}
function _requireMultisig() internal view {
if (!IPlatform(platform()).isOperator(msg.sender)) {
revert NotMultisig();
}
}
function _requireGovernanceOrMultisig() internal view {
IPlatform _platform = IPlatform(platform());
// nosemgrep
if (_platform.governance() != msg.sender && _platform.multisig() != msg.sender) {
revert NotGovernanceAndNotMultisig();
}
}
function _requireOperator() internal view {
if (!IPlatform(platform()).isOperator(msg.sender)) {
revert NotOperator();
}
}
function _requireFactory() internal view {
if (IPlatform(platform()).factory() != msg.sender) {
revert NotFactory();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
library ConstantsLib {
uint internal constant DENOMINATOR = 100_000;
address internal constant DEAD_ADDRESS = 0xdEad000000000000000000000000000000000000;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
library AmmAdapterIdLib {
string public constant UNISWAPV3 = "UniswapV3";
string public constant ALGEBRA = "Algebra";
string public constant KYBER = "KyberSwap";
string public constant CURVE = "Curve";
string public constant SOLIDLY = "Solidly";
string public constant BALANCER_COMPOSABLE_STABLE = "BalancerComposableStable";
string public constant BALANCER_WEIGHTED = "BalancerWeighted";
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
/**
* @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
* supported.
*/
function _require(bool condition, uint errorCode) pure {
if (!condition) _revert(errorCode);
}
/**
* @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
*/
function _revert(uint errorCode) pure {
// We're going to dynamically create a revert string based on the error code, with the following format:
// 'BAL#{errorCode}'
// where the code is left-padded with zeroes to three digits (so they range from 000 to 999).
//
// We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a
// number (8 to 16 bits) than the individual string characters.
//
// The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a
// much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a
// safe place to rely on it without worrying about how its usage might affect e.g. memory contents.
assembly {
// First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999
// range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for
// the '0' character.
let units := add(mod(errorCode, 10), 0x30)
errorCode := div(errorCode, 10)
let tenths := add(mod(errorCode, 10), 0x30)
errorCode := div(errorCode, 10)
let hundreds := add(mod(errorCode, 10), 0x30)
// With the individual characters, we can now construct the full string. The "BAL#" part is a known constant
// (0x42414c23): we simply shift this by 24 (to provide space for the 3 bytes of the error code), and add the
// characters to it, each shifted by a multiple of 8.
// The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits
// per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte
// array).
let revertReason := shl(200, add(0x42414c23000000, add(add(units, shl(8, tenths)), shl(16, hundreds))))
// We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded
// message will have the following layout:
// [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]
// The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We
// also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.
mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// The string length is fixed: 7 characters.
mstore(0x24, 7)
// Finally, the string itself is stored.
mstore(0x44, revertReason)
// Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of
// the encoded message is therefore 4 + 32 + 32 + 32 = 100.
revert(0, 100)
}
}
library Errors {
// Math
uint internal constant ADD_OVERFLOW = 0;
uint internal constant SUB_OVERFLOW = 1;
uint internal constant SUB_UNDERFLOW = 2;
uint internal constant MUL_OVERFLOW = 3;
uint internal constant ZERO_DIVISION = 4;
uint internal constant DIV_INTERNAL = 5;
uint internal constant X_OUT_OF_BOUNDS = 6;
uint internal constant Y_OUT_OF_BOUNDS = 7;
uint internal constant PRODUCT_OUT_OF_BOUNDS = 8;
uint internal constant INVALID_EXPONENT = 9;
// Input
uint internal constant OUT_OF_BOUNDS = 100;
uint internal constant UNSORTED_ARRAY = 101;
uint internal constant UNSORTED_TOKENS = 102;
uint internal constant INPUT_LENGTH_MISMATCH = 103;
uint internal constant ZERO_TOKEN = 104;
// Shared pools
uint internal constant MIN_TOKENS = 200;
uint internal constant MAX_TOKENS = 201;
uint internal constant MAX_SWAP_FEE_PERCENTAGE = 202;
uint internal constant MIN_SWAP_FEE_PERCENTAGE = 203;
uint internal constant MINIMUM_BPT = 204;
uint internal constant CALLER_NOT_VAULT = 205;
uint internal constant UNINITIALIZED = 206;
uint internal constant BPT_IN_MAX_AMOUNT = 207;
uint internal constant BPT_OUT_MIN_AMOUNT = 208;
uint internal constant EXPIRED_PERMIT = 209;
uint internal constant NOT_TWO_TOKENS = 210;
// Pools
uint internal constant MIN_AMP = 300;
uint internal constant MAX_AMP = 301;
uint internal constant MIN_WEIGHT = 302;
uint internal constant MAX_STABLE_TOKENS = 303;
uint internal constant MAX_IN_RATIO = 304;
uint internal constant MAX_OUT_RATIO = 305;
uint internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;
uint internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;
uint internal constant NORMALIZED_WEIGHT_INVARIANT = 308;
uint internal constant INVALID_TOKEN = 309;
uint internal constant UNHANDLED_JOIN_KIND = 310;
uint internal constant ZERO_INVARIANT = 311;
uint internal constant ORACLE_INVALID_SECONDS_QUERY = 312;
uint internal constant ORACLE_NOT_INITIALIZED = 313;
uint internal constant ORACLE_QUERY_TOO_OLD = 314;
uint internal constant ORACLE_INVALID_INDEX = 315;
uint internal constant ORACLE_BAD_SECS = 316;
uint internal constant AMP_END_TIME_TOO_CLOSE = 317;
uint internal constant AMP_ONGOING_UPDATE = 318;
uint internal constant AMP_RATE_TOO_HIGH = 319;
uint internal constant AMP_NO_ONGOING_UPDATE = 320;
uint internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;
uint internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;
uint internal constant RELAYER_NOT_CONTRACT = 323;
uint internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;
uint internal constant REBALANCING_RELAYER_REENTERED = 325;
uint internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326;
uint internal constant SWAPS_DISABLED = 327;
uint internal constant CALLER_IS_NOT_LBP_OWNER = 328;
uint internal constant PRICE_RATE_OVERFLOW = 329;
uint internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330;
uint internal constant WEIGHT_CHANGE_TOO_FAST = 331;
uint internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332;
uint internal constant UPPER_TARGET_TOO_HIGH = 333;
uint internal constant UNHANDLED_BY_LINEAR_POOL = 334;
uint internal constant OUT_OF_TARGET_RANGE = 335;
uint internal constant UNHANDLED_EXIT_KIND = 336;
uint internal constant UNAUTHORIZED_EXIT = 337;
uint internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338;
uint internal constant UNHANDLED_BY_INVESTMENT_POOL = 339;
uint internal constant UNHANDLED_BY_PHANTOM_POOL = 340;
uint internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341;
uint internal constant INVALID_INITIALIZATION = 342;
// Lib
uint internal constant REENTRANCY = 400;
uint internal constant SENDER_NOT_ALLOWED = 401;
uint internal constant PAUSED = 402;
uint internal constant PAUSE_WINDOW_EXPIRED = 403;
uint internal constant MAX_PAUSE_WINDOW_DURATION = 404;
uint internal constant MAX_BUFFER_PERIOD_DURATION = 405;
uint internal constant INSUFFICIENT_BALANCE = 406;
uint internal constant INSUFFICIENT_ALLOWANCE = 407;
uint internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;
uint internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;
uint internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;
uint internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;
uint internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;
uint internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;
uint internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;
uint internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;
uint internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;
uint internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;
uint internal constant SAFE_ERC20_CALL_FAILED = 418;
uint internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;
uint internal constant ADDRESS_CANNOT_SEND_VALUE = 420;
uint internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;
uint internal constant GRANT_SENDER_NOT_ADMIN = 422;
uint internal constant REVOKE_SENDER_NOT_ADMIN = 423;
uint internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;
uint internal constant BUFFER_PERIOD_EXPIRED = 425;
uint internal constant CALLER_IS_NOT_OWNER = 426;
uint internal constant NEW_OWNER_IS_ZERO = 427;
uint internal constant CODE_DEPLOYMENT_FAILED = 428;
uint internal constant CALL_TO_NON_CONTRACT = 429;
uint internal constant LOW_LEVEL_CALL_FAILED = 430;
// Vault
uint internal constant INVALID_POOL_ID = 500;
uint internal constant CALLER_NOT_POOL = 501;
uint internal constant SENDER_NOT_ASSET_MANAGER = 502;
uint internal constant USER_DOESNT_ALLOW_RELAYER = 503;
uint internal constant INVALID_SIGNATURE = 504;
uint internal constant EXIT_BELOW_MIN = 505;
uint internal constant JOIN_ABOVE_MAX = 506;
uint internal constant SWAP_LIMIT = 507;
uint internal constant SWAP_DEADLINE = 508;
uint internal constant CANNOT_SWAP_SAME_TOKEN = 509;
uint internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;
uint internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;
uint internal constant INTERNAL_BALANCE_OVERFLOW = 512;
uint internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;
uint internal constant INVALID_ETH_INTERNAL_BALANCE = 514;
uint internal constant INVALID_POST_LOAN_BALANCE = 515;
uint internal constant INSUFFICIENT_ETH = 516;
uint internal constant UNALLOCATED_ETH = 517;
uint internal constant ETH_TRANSFER = 518;
uint internal constant CANNOT_USE_ETH_SENTINEL = 519;
uint internal constant TOKENS_MISMATCH = 520;
uint internal constant TOKEN_NOT_REGISTERED = 521;
uint internal constant TOKEN_ALREADY_REGISTERED = 522;
uint internal constant TOKENS_ALREADY_SET = 523;
uint internal constant TOKENS_LENGTH_MUST_BE_2 = 524;
uint internal constant NONZERO_TOKEN_BALANCE = 525;
uint internal constant BALANCE_TOTAL_OVERFLOW = 526;
uint internal constant POOL_NO_TOKENS = 527;
uint internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;
// Fees
uint internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;
uint internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;
uint internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import "./BalancerErrors.sol";
import "./LogExpMath.sol";
library FixedPoint {
uint internal constant ONE = 1e18; // 18 decimal places
uint internal constant MAX_POW_RELATIVE_ERROR = 10000; // 10^(-14)
// Minimum base for the power function when the exponent is 'free' (larger than ONE).
uint internal constant MIN_POW_BASE_FREE_EXPONENT = 0.7e18;
function add(uint a, uint b) internal pure returns (uint) {
// Fixed Point addition is the same as regular checked addition
uint c = a + b;
_require(c >= a, Errors.ADD_OVERFLOW);
return c;
}
function sub(uint a, uint b) internal pure returns (uint) {
// Fixed Point addition is the same as regular checked addition
_require(b <= a, Errors.SUB_OVERFLOW);
uint c = a - b;
return c;
}
function mulDown(uint a, uint b) internal pure returns (uint) {
uint product = a * b;
_require(a == 0 || product / a == b, Errors.MUL_OVERFLOW);
return product / ONE;
}
function mulUp(uint a, uint b) internal pure returns (uint) {
uint product = a * b;
_require(a == 0 || product / a == b, Errors.MUL_OVERFLOW);
if (product == 0) {
return 0;
} else {
// The traditional divUp formula is:
// divUp(x, y) := (x + y - 1) / y
// To avoid intermediate overflow in the addition, we distribute the division and get:
// divUp(x, y) := (x - 1) / y + 1
// Note that this requires x != 0, which we already tested for.
return ((product - 1) / ONE) + 1;
}
}
function divDown(uint a, uint b) internal pure returns (uint) {
_require(b != 0, Errors.ZERO_DIVISION);
if (a == 0) {
return 0;
} else {
uint aInflated = a * ONE;
_require(aInflated / a == ONE, Errors.DIV_INTERNAL); // mul overflow
return aInflated / b;
}
}
function divUp(uint a, uint b) internal pure returns (uint) {
_require(b != 0, Errors.ZERO_DIVISION);
if (a == 0) {
return 0;
} else {
uint aInflated = a * ONE;
_require(aInflated / a == ONE, Errors.DIV_INTERNAL); // mul overflow
// The traditional divUp formula is:
// divUp(x, y) := (x + y - 1) / y
// To avoid intermediate overflow in the addition, we distribute the division and get:
// divUp(x, y) := (x - 1) / y + 1
// Note that this requires x != 0, which we already tested for.
return ((aInflated - 1) / b) + 1;
}
}
/**
* @dev Returns x^y, assuming both are fixed point numbers, rounding down. The result is guaranteed to not be above
* the true value (that is, the error function expected - actual is always positive).
*/
function powDown(uint x, uint y) internal pure returns (uint) {
uint raw = LogExpMath.pow(x, y);
uint maxError = add(mulUp(raw, MAX_POW_RELATIVE_ERROR), 1);
if (raw < maxError) {
return 0;
} else {
return sub(raw, maxError);
}
}
/**
* @dev Returns x^y, assuming both are fixed point numbers, rounding up. The result is guaranteed to not be below
* the true value (that is, the error function expected - actual is always negative).
*/
function powUp(uint x, uint y) internal pure returns (uint) {
uint raw = LogExpMath.pow(x, y);
uint maxError = add(mulUp(raw, MAX_POW_RELATIVE_ERROR), 1);
return add(raw, maxError);
}
/**
* @dev Returns the complement of a value (1 - x), capped to 0 if x is larger than 1.
*
* Useful when computing the complement for values with some level of relative error, as it strips this error and
* prevents intermediate negative values.
*/
function complement(uint x) internal pure returns (uint) {
return (x < ONE) ? (ONE - x) : 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "./FixedPoint.sol";
/// @dev Library for up scaling / downscaling amounts for tokens with different decimals
/// @dev Used for Balancer swappers
/// @dev taken from https://github.com/balancer-labs/balancer-v2-monorepo/blob/c18ff2686c61a8cbad72cdcfc65e9b11476fdbc3/pkg/pool-utils/contracts/BasePool.sol#L520
library ScaleLib {
function _upscale(uint amount, uint scalingFactor) internal pure returns (uint) {
return FixedPoint.mulDown(amount, scalingFactor);
}
function _upscaleArray(uint[] memory amounts, uint[] memory scalingFactors) internal pure {
uint len = amounts.length;
for (uint i = 0; i < len; ++i) {
amounts[i] = FixedPoint.mulDown(amounts[i], scalingFactors[i]);
}
}
function _downscaleDown(uint amount, uint scalingFactor) internal pure returns (uint) {
return FixedPoint.divDown(amount, scalingFactor);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
import "./FixedPoint.sol";
import {LegacyOZMath} from "./LegacyOZMath.sol";
library StableMath {
using FixedPoint for uint;
uint internal constant _MIN_AMP = 1;
uint internal constant _MAX_AMP = 5000;
uint internal constant _AMP_PRECISION = 1e3;
uint internal constant _MAX_STABLE_TOKENS = 5;
// Note on unchecked arithmetic:
// This contract performs a large number of additions, subtractions, multiplications and divisions, often inside
// loops. Since many of these operations are gas-sensitive (as they happen e.g. during a swap), it is important to
// not make any unnecessary checks. We rely on a set of invariants to avoid having to use checked arithmetic (the
// Math library), including:
// - the number of tokens is bounded by _MAX_STABLE_TOKENS
// - the amplification parameter is bounded by _MAX_AMP * _AMP_PRECISION, which fits in 23 bits
// - the token balances are bounded by 2^112 (guaranteed by the Vault) times 1e18 (the maximum scaling factor),
// which fits in 172 bits
//
// This means e.g. we can safely multiply a balance by the amplification parameter without worrying about overflow.
// About swap fees on joins and exits:
// Any join or exit that is not perfectly balanced (e.g. all single token joins or exits) is mathematically
// equivalent to a perfectly balanced join or exit followed by a series of swaps. Since these swaps would charge
// swap fees, it follows that (some) joins and exits should as well.
// On these operations, we split the token amounts in 'taxable' and 'non-taxable' portions, where the 'taxable' part
// is the one to which swap fees are applied.
// Computes the invariant given the current balances, using the Newton-Raphson approximation.
// The amplification parameter equals: A n^(n-1)
function _calculateInvariant(
uint amplificationParameter,
uint[] memory balances,
bool roundUp
) internal pure returns (uint) {
/**
*
* // invariant //
* // D = invariant D^(n+1) //
* // A = amplification coefficient A n^n S + D = A D n^n + ----------- //
* // S = sum of balances n^n P //
* // P = product of balances //
* // n = number of tokens //
*
*/
// We support rounding up or down.
uint sum = 0;
uint numTokens = balances.length;
for (uint i = 0; i < numTokens; i++) {
sum = sum.add(balances[i]);
}
if (sum == 0) {
return 0;
}
uint prevInvariant = 0;
uint invariant = sum;
uint ampTimesTotal = amplificationParameter * numTokens;
for (uint i = 0; i < 255; i++) {
uint P_D = balances[0] * numTokens;
for (uint j = 1; j < numTokens; j++) {
P_D = LegacyOZMath.div(
LegacyOZMath.mul(LegacyOZMath.mul(P_D, balances[j]), numTokens), invariant, roundUp
);
}
prevInvariant = invariant;
invariant = LegacyOZMath.div(
LegacyOZMath.mul(LegacyOZMath.mul(numTokens, invariant), invariant).add(
LegacyOZMath.div(
LegacyOZMath.mul(LegacyOZMath.mul(ampTimesTotal, sum), P_D), _AMP_PRECISION, roundUp
)
),
LegacyOZMath.mul(numTokens + 1, invariant).add(
// No need to use checked arithmetic for the amp precision, the amp is guaranteed to be at least 1
LegacyOZMath.div(LegacyOZMath.mul(ampTimesTotal - _AMP_PRECISION, P_D), _AMP_PRECISION, !roundUp)
),
roundUp
);
if (invariant > prevInvariant) {
if (invariant - prevInvariant <= 1) {
return invariant;
}
} else if (prevInvariant - invariant <= 1) {
return invariant;
}
}
_revert(Errors.STABLE_INVARIANT_DIDNT_CONVERGE);
}
// Computes how many tokens can be taken out of a pool if `tokenAmountIn` are sent, given the current balances.
// The amplification parameter equals: A n^(n-1)
// The invariant should be rounded up.
function _calcOutGivenIn(
uint amplificationParameter,
uint[] memory balances,
uint tokenIndexIn,
uint tokenIndexOut,
uint tokenAmountIn,
uint invariant
) internal pure returns (uint) {
/**
*
* // outGivenIn token x for y - polynomial equation to solve //
* // ay = amount out to calculate //
* // by = balance token out //
* // y = by - ay (finalBalanceOut) //
* // D = invariant D D^(n+1) //
* // A = amplification coefficient y^2 + ( S - ---------- - D) * y - ------------- = 0 //
* // n = number of tokens (A * n^n) A * n^2n * P //
* // S = sum of final balances but y //
* // P = product of final balances but y //
*
*/
// Amount out, so we round down overall.
balances[tokenIndexIn] = balances[tokenIndexIn].add(tokenAmountIn);
uint finalBalanceOut = _getTokenBalanceGivenInvariantAndAllOtherBalances(
amplificationParameter, balances, invariant, tokenIndexOut
);
// No need to use checked arithmetic since `tokenAmountIn` was actually added to the same balance right before
// calling `_getTokenBalanceGivenInvariantAndAllOtherBalances` which doesn't alter the balances array.
balances[tokenIndexIn] = balances[tokenIndexIn] - tokenAmountIn;
return balances[tokenIndexOut].sub(finalBalanceOut).sub(1);
}
// Computes how many tokens must be sent to a pool if `tokenAmountOut` are sent given the
// current balances, using the Newton-Raphson approximation.
// The amplification parameter equals: A n^(n-1)
// The invariant should be rounded up.
function _calcInGivenOut(
uint amplificationParameter,
uint[] memory balances,
uint tokenIndexIn,
uint tokenIndexOut,
uint tokenAmountOut,
uint invariant
) internal pure returns (uint) {
/**
*
* // inGivenOut token x for y - polynomial equation to solve //
* // ax = amount in to calculate //
* // bx = balance token in //
* // x = bx + ax (finalBalanceIn) //
* // D = invariant D D^(n+1) //
* // A = amplification coefficient x^2 + ( S - ---------- - D) * x - ------------- = 0 //
* // n = number of tokens (A * n^n) A * n^2n * P //
* // S = sum of final balances but x //
* // P = product of final balances but x //
*
*/
// Amount in, so we round up overall.
balances[tokenIndexOut] = balances[tokenIndexOut].sub(tokenAmountOut);
uint finalBalanceIn =
_getTokenBalanceGivenInvariantAndAllOtherBalances(amplificationParameter, balances, invariant, tokenIndexIn);
// No need to use checked arithmetic since `tokenAmountOut` was actually subtracted from the same balance right
// before calling `_getTokenBalanceGivenInvariantAndAllOtherBalances` which doesn't alter the balances array.
balances[tokenIndexOut] = balances[tokenIndexOut] + tokenAmountOut;
return finalBalanceIn.sub(balances[tokenIndexIn]).add(1);
}
function _calcBptOutGivenExactTokensIn(
uint amp,
uint[] memory balances,
uint[] memory amountsIn,
uint bptTotalSupply,
uint swapFeePercentage
) internal pure returns (uint) {
// BPT out, so we round down overall.
// First loop calculates the sum of all token balances, which will be used to calculate
// the current weights of each token, relative to this sum
uint sumBalances = 0;
for (uint i = 0; i < balances.length; i++) {
sumBalances = sumBalances.add(balances[i]);
}
// Calculate the weighted balance ratio without considering fees
uint[] memory balanceRatiosWithFee = new uint[](amountsIn.length);
// The weighted sum of token balance ratios with fee
uint invariantRatioWithFees = 0;
for (uint i = 0; i < balances.length; i++) {
uint currentWeight = balances[i].divDown(sumBalances);
balanceRatiosWithFee[i] = balances[i].add(amountsIn[i]).divDown(balances[i]);
invariantRatioWithFees = invariantRatioWithFees.add(balanceRatiosWithFee[i].mulDown(currentWeight));
}
// Second loop calculates new amounts in, taking into account the fee on the percentage excess
uint[] memory newBalances = new uint[](balances.length);
for (uint i = 0; i < balances.length; i++) {
uint amountInWithoutFee;
// Check if the balance ratio is greater than the ideal ratio to charge fees or not
if (balanceRatiosWithFee[i] > invariantRatioWithFees) {
uint nonTaxableAmount = balances[i].mulDown(invariantRatioWithFees.sub(FixedPoint.ONE));
uint taxableAmount = amountsIn[i].sub(nonTaxableAmount);
// No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%
amountInWithoutFee = nonTaxableAmount.add(taxableAmount.mulDown(FixedPoint.ONE - swapFeePercentage));
} else {
amountInWithoutFee = amountsIn[i];
}
newBalances[i] = balances[i].add(amountInWithoutFee);
}
// Get current and new invariants, taking swap fees into account
uint currentInvariant = _calculateInvariant(amp, balances, true);
uint newInvariant = _calculateInvariant(amp, newBalances, false);
uint invariantRatio = newInvariant.divDown(currentInvariant);
// If the invariant didn't increase for any reason, we simply don't mint BPT
if (invariantRatio > FixedPoint.ONE) {
return bptTotalSupply.mulDown(invariantRatio - FixedPoint.ONE);
} else {
return 0;
}
}
function _calcTokenInGivenExactBptOut(
uint amp,
uint[] memory balances,
uint tokenIndex,
uint bptAmountOut,
uint bptTotalSupply,
uint swapFeePercentage
) internal pure returns (uint) {
// Token in, so we round up overall.
// Get the current invariant
uint currentInvariant = _calculateInvariant(amp, balances, true);
// Calculate new invariant
uint newInvariant = bptTotalSupply.add(bptAmountOut).divUp(bptTotalSupply).mulUp(currentInvariant);
// Calculate amount in without fee.
uint newBalanceTokenIndex =
_getTokenBalanceGivenInvariantAndAllOtherBalances(amp, balances, newInvariant, tokenIndex);
uint amountInWithoutFee = newBalanceTokenIndex.sub(balances[tokenIndex]);
// First calculate the sum of all token balances, which will be used to calculate
// the current weight of each token
uint sumBalances = 0;
for (uint i = 0; i < balances.length; i++) {
sumBalances = sumBalances.add(balances[i]);
}
// We can now compute how much extra balance is being deposited and used in virtual swaps, and charge swap fees
// accordingly.
uint currentWeight = balances[tokenIndex].divDown(sumBalances);
uint taxablePercentage = currentWeight.complement();
uint taxableAmount = amountInWithoutFee.mulUp(taxablePercentage);
uint nonTaxableAmount = amountInWithoutFee.sub(taxableAmount);
// No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%
return nonTaxableAmount.add(taxableAmount.divUp(FixedPoint.ONE - swapFeePercentage));
}
/*
Flow of calculations:
amountsTokenOut -> amountsOutProportional ->
amountOutPercentageExcess -> amountOutBeforeFee -> newInvariant -> amountBPTIn
*/
function _calcBptInGivenExactTokensOut(
uint amp,
uint[] memory balances,
uint[] memory amountsOut,
uint bptTotalSupply,
uint swapFeePercentage
) internal pure returns (uint) {
// BPT in, so we round up overall.
// First loop calculates the sum of all token balances, which will be used to calculate
// the current weights of each token relative to this sum
uint sumBalances = 0;
for (uint i = 0; i < balances.length; i++) {
sumBalances = sumBalances.add(balances[i]);
}
// Calculate the weighted balance ratio without considering fees
uint[] memory balanceRatiosWithoutFee = new uint[](amountsOut.length);
uint invariantRatioWithoutFees = 0;
for (uint i = 0; i < balances.length; i++) {
uint currentWeight = balances[i].divUp(sumBalances);
balanceRatiosWithoutFee[i] = balances[i].sub(amountsOut[i]).divUp(balances[i]);
invariantRatioWithoutFees = invariantRatioWithoutFees.add(balanceRatiosWithoutFee[i].mulUp(currentWeight));
}
// Second loop calculates new amounts in, taking into account the fee on the percentage excess
uint[] memory newBalances = new uint[](balances.length);
for (uint i = 0; i < balances.length; i++) {
// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it to
// 'token out'. This results in slightly larger price impact.
uint amountOutWithFee;
if (invariantRatioWithoutFees > balanceRatiosWithoutFee[i]) {
uint nonTaxableAmount = balances[i].mulDown(invariantRatioWithoutFees.complement());
uint taxableAmount = amountsOut[i].sub(nonTaxableAmount);
// No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%
amountOutWithFee = nonTaxableAmount.add(taxableAmount.divUp(FixedPoint.ONE - swapFeePercentage));
} else {
amountOutWithFee = amountsOut[i];
}
newBalances[i] = balances[i].sub(amountOutWithFee);
}
// Get current and new invariants, taking into account swap fees
uint currentInvariant = _calculateInvariant(amp, balances, true);
uint newInvariant = _calculateInvariant(amp, newBalances, false);
uint invariantRatio = newInvariant.divDown(currentInvariant);
// return amountBPTIn
return bptTotalSupply.mulUp(invariantRatio.complement());
}
function _calcTokenOutGivenExactBptIn(
uint amp,
uint[] memory balances,
uint tokenIndex,
uint bptAmountIn,
uint bptTotalSupply,
uint swapFeePercentage
) internal pure returns (uint) {
// Token out, so we round down overall.
// Get the current and new invariants. Since we need a bigger new invariant, we round the current one up.
uint currentInvariant = _calculateInvariant(amp, balances, true);
uint newInvariant = bptTotalSupply.sub(bptAmountIn).divUp(bptTotalSupply).mulUp(currentInvariant);
// Calculate amount out without fee
uint newBalanceTokenIndex =
_getTokenBalanceGivenInvariantAndAllOtherBalances(amp, balances, newInvariant, tokenIndex);
uint amountOutWithoutFee = balances[tokenIndex].sub(newBalanceTokenIndex);
// First calculate the sum of all token balances, which will be used to calculate
// the current weight of each token
uint sumBalances = 0;
for (uint i = 0; i < balances.length; i++) {
sumBalances = sumBalances.add(balances[i]);
}
// We can now compute how much excess balance is being withdrawn as a result of the virtual swaps, which result
// in swap fees.
uint currentWeight = balances[tokenIndex].divDown(sumBalances);
uint taxablePercentage = currentWeight.complement();
// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it
// to 'token out'. This results in slightly larger price impact. Fees are rounded up.
uint taxableAmount = amountOutWithoutFee.mulUp(taxablePercentage);
uint nonTaxableAmount = amountOutWithoutFee.sub(taxableAmount);
// No need to use checked arithmetic for the swap fee, it is guaranteed to be lower than 50%
return nonTaxableAmount.add(taxableAmount.mulDown(FixedPoint.ONE - swapFeePercentage));
}
function _calcTokensOutGivenExactBptIn(
uint[] memory balances,
uint bptAmountIn,
uint bptTotalSupply
) internal pure returns (uint[] memory) {
/**
*
* // exactBPTInForTokensOut //
* // (per token) //
* // aO = tokenAmountOut / bptIn \ //
* // b = tokenBalance a0 = b * | --------------------- | //
* // bptIn = bptAmountIn \ bptTotalSupply / //
* // bpt = bptTotalSupply //
*
*/
// Since we're computing an amount out, we round down overall. This means rounding down on both the
// multiplication and division.
uint bptRatio = bptAmountIn.divDown(bptTotalSupply);
uint[] memory amountsOut = new uint[](balances.length);
for (uint i = 0; i < balances.length; i++) {
amountsOut[i] = balances[i].mulDown(bptRatio);
}
return amountsOut;
}
// The amplification parameter equals: A n^(n-1)
function _calcDueTokenProtocolSwapFeeAmount(
uint amplificationParameter,
uint[] memory balances,
uint lastInvariant,
uint tokenIndex,
uint protocolSwapFeePercentage
) internal pure returns (uint) {
/**
*
* // oneTokenSwapFee - polynomial equation to solve //
* // af = fee amount to calculate in one token //
* // bf = balance of fee token //
* // f = bf - af (finalBalanceFeeToken) //
* // D = old invariant D D^(n+1) //
* // A = amplification coefficient f^2 + ( S - ---------- - D) * f - ------------- = 0 //
* // n = number of tokens (A * n^n) A * n^2n * P //
* // S = sum of final balances but f //
* // P = product of final balances but f //
*
*/
// Protocol swap fee amount, so we round down overall.
uint finalBalanceFeeToken = _getTokenBalanceGivenInvariantAndAllOtherBalances(
amplificationParameter, balances, lastInvariant, tokenIndex
);
if (balances[tokenIndex] <= finalBalanceFeeToken) {
// This shouldn't happen outside of rounding errors, but have this safeguard nonetheless to prevent the Pool
// from entering a locked state in which joins and exits revert while computing accumulated swap fees.
return 0;
}
// Result is rounded down
uint accumulatedTokenSwapFees = balances[tokenIndex] - finalBalanceFeeToken;
return accumulatedTokenSwapFees.mulDown(protocolSwapFeePercentage);
}
// Private functions
// This function calculates the balance of a given token (tokenIndex)
// given all the other balances and the invariant
function _getTokenBalanceGivenInvariantAndAllOtherBalances(
uint amplificationParameter,
uint[] memory balances,
uint invariant,
uint tokenIndex
) internal pure returns (uint) {
// Rounds result up overall
uint ampTimesTotal = amplificationParameter * balances.length;
uint sum = balances[0];
uint P_D = balances[0] * balances.length;
for (uint j = 1; j < balances.length; j++) {
P_D = LegacyOZMath.divDown(LegacyOZMath.mul(LegacyOZMath.mul(P_D, balances[j]), balances.length), invariant);
sum = sum.add(balances[j]);
}
// No need to use safe math, based on the loop above `sum` is greater than or equal to `balances[tokenIndex]`
sum = sum - balances[tokenIndex];
uint inv2 = LegacyOZMath.mul(invariant, invariant);
// We remove the balance from c by multiplying it
uint c = LegacyOZMath.mul(
LegacyOZMath.mul(LegacyOZMath.divUp(inv2, LegacyOZMath.mul(ampTimesTotal, P_D)), _AMP_PRECISION),
balances[tokenIndex]
);
uint b = sum.add(LegacyOZMath.mul(LegacyOZMath.divDown(invariant, ampTimesTotal), _AMP_PRECISION));
// We iterate to find the balance
uint prevTokenBalance = 0;
// We multiply the first iteration outside the loop with the invariant to set the value of the
// initial approximation.
uint tokenBalance = LegacyOZMath.divUp(inv2.add(c), invariant.add(b));
for (uint i = 0; i < 255; i++) {
prevTokenBalance = tokenBalance;
tokenBalance = LegacyOZMath.divUp(
LegacyOZMath.mul(tokenBalance, tokenBalance).add(c),
LegacyOZMath.mul(tokenBalance, 2).add(b).sub(invariant)
);
if (tokenBalance > prevTokenBalance) {
if (tokenBalance - prevTokenBalance <= 1) {
return tokenBalance;
}
} else if (prevTokenBalance - tokenBalance <= 1) {
return tokenBalance;
}
}
_revert(Errors.STABLE_GET_BALANCE_DIDNT_CONVERGE);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;
interface IBComposableStablePoolMinimal {
/**
* @dev Returns all normalized weights, in the same order as the Pool's tokens.
*/
function getPoolId() external view returns (bytes32);
function getSwapFeePercentage() external view returns (uint);
function getAmplificationParameter() external view returns (uint value, bool isUpdating, uint precision);
function getScalingFactors() external view returns (uint[] memory);
function getBptIndex() external view returns (uint);
function getVault() external view returns (address);
function updateTokenRateCache(address token) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IAsset {}
interface IBVault {
// Internal Balance
//
// Users can deposit tokens into the Vault, where they are allocated to their Internal Balance, and later
// transferred or withdrawn. It can also be used as a source of tokens when joining Pools, as a destination
// when exiting them, and as either when performing swaps. This usage of Internal Balance results in greatly reduced
// gas costs when compared to relying on plain ERC20 transfers, leading to large savings for frequent users.
//
// Internal Balance management features batching, which means a single contract call can be used to perform multiple
// operations of different kinds, with different senders and recipients, at once.
/**
* @dev Returns `user`'s Internal Balance for a set of tokens.
*/
function getInternalBalance(address user, IERC20[] calldata tokens) external view returns (uint[] memory);
/**
* @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)
* and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as
* it lets integrators reuse a user's Vault allowance.
*
* For each operation, if the caller is not `sender`, it must be an authorized relayer for them.
*/
function manageUserBalance(UserBalanceOp[] calldata ops) external payable;
/**
* @dev Data for `manageUserBalance` operations, which include the possibility for ETH to be sent and received
* without manual WETH wrapping or unwrapping.
*/
struct UserBalanceOp {
UserBalanceOpKind kind;
IAsset asset;
uint amount;
address sender;
address payable recipient;
}
// There are four possible operations in `manageUserBalance`:
//
// - DEPOSIT_INTERNAL
// Increases the Internal Balance of the `recipient` account by transferring tokens from the corresponding
// `sender`. The sender must have allowed the Vault to use their tokens via `IERC20.approve()`.
//
// ETH can be used by passing the ETH sentinel value as the asset and forwarding ETH in the call: it will be wrapped
// and deposited as WETH. Any ETH amount remaining will be sent back to the caller (not the sender, which is
// relevant for relayers).
//
// Emits an `InternalBalanceChanged` event.
//
//
// - WITHDRAW_INTERNAL
// Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient`.
//
// ETH can be used by passing the ETH sentinel value as the asset. This will deduct WETH instead, unwrap it and send
// it to the recipient as ETH.
//
// Emits an `InternalBalanceChanged` event.
//
//
// - TRANSFER_INTERNAL
// Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient`.
//
// Reverts if the ETH sentinel value is passed.
//
// Emits an `InternalBalanceChanged` event.
//
//
// - TRANSFER_EXTERNAL
// Transfers tokens from `sender` to `recipient`, using the Vault's ERC20 allowance. This is typically used by
// relayers, as it lets them reuse a user's Vault allowance.
//
// Reverts if the ETH sentinel value is passed.
//
// Emits an `ExternalBalanceTransfer` event.
enum UserBalanceOpKind {
DEPOSIT_INTERNAL,
WITHDRAW_INTERNAL,
TRANSFER_INTERNAL,
TRANSFER_EXTERNAL
}
/**
* @dev Emitted when a user's Internal Balance changes, either from calls to `manageUserBalance`, or through
* interacting with Pools using Internal Balance.
*
* Because Internal Balance works exclusively with ERC20 tokens, ETH deposits and withdrawals will use the WETH
* address.
*/
event InternalBalanceChanged(address indexed user, IERC20 indexed token, int delta);
/**
* @dev Emitted when a user's Vault ERC20 allowance is used by the Vault to transfer tokens to an external account.
*/
event ExternalBalanceTransfer(IERC20 indexed token, address indexed sender, address recipient, uint amount);
// Pools
//
// There are three specialization settings for Pools, which allow for cheaper swaps at the cost of reduced
// functionality:
//
// - General: no specialization, suited for all Pools. IGeneralPool is used for swap request callbacks, passing the
// balance of all tokens in the Pool. These Pools have the largest swap costs (because of the extra storage reads),
// which increase with the number of registered tokens.
//
// - Minimal Swap Info: IMinimalSwapInfoPool is used instead of IGeneralPool, which saves gas by only passing the
// balance of the two tokens involved in the swap. This is suitable for some pricing algorithms, like the weighted
// constant product one popularized by Balancer V1. Swap costs are smaller compared to general Pools, and are
// independent of the number of registered tokens.
//
// - Two Token: only allows two tokens to be registered. This achieves the lowest possible swap gas cost. Like
// minimal swap info Pools, these are called via IMinimalSwapInfoPool.
enum PoolSpecialization {
GENERAL,
MINIMAL_SWAP_INFO,
TWO_TOKEN
}
/**
* @dev Registers the caller account as a Pool with a given specialization setting. Returns the Pool's ID, which
* is used in all Pool-related functions. Pools cannot be deregistered, nor can the Pool's specialization be
* changed.
*
* The caller is expected to be a smart contract that implements either `IGeneralPool` or `IMinimalSwapInfoPool`,
* depending on the chosen specialization setting. This contract is known as the Pool's contract.
*
* Note that the same contract may register itself as multiple Pools with unique Pool IDs, or in other words,
* multiple Pools may share the same contract.
*
* Emits a `PoolRegistered` event.
*/
function registerPool(PoolSpecialization specialization) external returns (bytes32);
/**
* @dev Emitted when a Pool is registered by calling `registerPool`.
*/
event PoolRegistered(bytes32 indexed poolId, address indexed poolAddress, PoolSpecialization specialization);
/**
* @dev Returns a Pool's contract address and specialization setting.
*/
function getPool(bytes32 poolId) external view returns (address, PoolSpecialization);
/**
* @dev Registers `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
*
* Pools can only interact with tokens they have registered. Users join a Pool by transferring registered tokens,
* exit by receiving registered tokens, and can only swap registered tokens.
*
* Each token can only be registered once. For Pools with the Two Token specialization, `tokens` must have a length
* of two, that is, both tokens must be registered in the same `registerTokens` call, and they must be sorted in
* ascending order.
*
* The `tokens` and `assetManagers` arrays must have the same length, and each entry in these indicates the Asset
* Manager for the corresponding token. Asset Managers can manage a Pool's tokens via `managePoolBalance`,
* depositing and withdrawing them directly, and can even set their balance to arbitrary amounts. They are therefore
* expected to be highly secured smart contracts with sound design principles, and the decision to register an
* Asset Manager should not be made lightly.
*
* Pools can choose not to assign an Asset Manager to a given token by passing in the zero address. Once an Asset
* Manager is set, it cannot be changed except by deregistering the associated token and registering again with a
* different Asset Manager.
*
* Emits a `TokensRegistered` event.
*/
function registerTokens(bytes32 poolId, IERC20[] calldata tokens, address[] calldata assetManagers) external;
/**
* @dev Emitted when a Pool registers tokens by calling `registerTokens`.
*/
event TokensRegistered(bytes32 indexed poolId, IERC20[] tokens, address[] assetManagers);
/**
* @dev Deregisters `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
*
* Only registered tokens (via `registerTokens`) can be deregistered. Additionally, they must have zero total
* balance. For Pools with the Two Token specialization, `tokens` must have a length of two, that is, both tokens
* must be deregistered in the same `deregisterTokens` call.
*
* A deregistered token can be re-registered later on, possibly with a different Asset Manager.
*
* Emits a `TokensDeregistered` event.
*/
function deregisterTokens(bytes32 poolId, IERC20[] calldata tokens) external;
/**
* @dev Emitted when a Pool deregisters tokens by calling `deregisterTokens`.
*/
event TokensDeregistered(bytes32 indexed poolId, IERC20[] tokens);
/**
* @dev Returns detailed information for a Pool's registered token.
*
* `cash` is the number of tokens the Vault currently holds for the Pool. `managed` is the number of tokens
* withdrawn and held outside the Vault by the Pool's token Asset Manager. The Pool's total balance for `token`
* equals the sum of `cash` and `managed`.
*
* Internally, `cash` and `managed` are stored using 112 bits. No action can ever cause a Pool's token `cash`,
* `managed` or `total` balance to be greater than 2^112 - 1.
*
* `lastChangeBlock` is the number of the block in which `token`'s total balance was last modified (via either a
* join, exit, swap, or Asset Manager update). This value is useful to avoid so-called 'sandwich attacks', for
* example when developing price oracles. A change of zero (e.g. caused by a swap with amount zero) is considered a
* change for this purpose, and will update `lastChangeBlock`.
*
* `assetManager` is the Pool's token Asset Manager.
*/
function getPoolTokenInfo(
bytes32 poolId,
IERC20 token
) external view returns (uint cash, uint managed, uint lastChangeBlock, address assetManager);
/**
* @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of
* the tokens' `balances` changed.
*
* The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all
* Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.
*
* If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same
* order as passed to `registerTokens`.
*
* Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are
* the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`
* instead.
* renamed IERC20[] to address[]
*/
function getPoolTokens(bytes32 poolId)
external
view
returns (address[] memory tokens, uint[] memory balances, uint lastChangeBlock);
/**
* @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will
* trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized
* Pool shares.
*
* If the caller is not `sender`, it must be an authorized relayer for them.
*
* The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount
* to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces
* these maximums.
*
* If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable
* this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the
* WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent
* back to the caller (not the sender, which is important for relayers).
*
* `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
* interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be
* sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final
* `assets` array might not be sorted. Pools with no registered tokens cannot be joined.
*
* If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only
* be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be
* withdrawn from Internal Balance: attempting to do so will trigger a revert.
*
* This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement
* their own custom logic. This typically requires additional information from the user (such as the expected number
* of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed
* directly to the Pool's contract, as is `recipient`.
*
* Emits a `PoolBalanceChanged` event.
*/
function joinPool(
bytes32 poolId,
address sender,
address recipient,
JoinPoolRequest calldata request
) external payable;
enum JoinKind {
INIT,
EXACT_TOKENS_IN_FOR_BPT_OUT,
TOKEN_IN_FOR_EXACT_BPT_OUT
}
enum ExitKind {
EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,
EXACT_BPT_IN_FOR_TOKENS_OUT,
BPT_IN_FOR_EXACT_TOKENS_OUT
}
/// @dev modified to address[]
struct JoinPoolRequest {
address[] assets;
uint[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}
/**
* @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will
* trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized
* Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see
* `getPoolTokenInfo`).
*
* If the caller is not `sender`, it must be an authorized relayer for them.
*
* The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum
* token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:
* it just enforces these minimums.
*
* If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To
* enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead
* of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.
*
* `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
* interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must
* be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the
* final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.
*
* If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,
* an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to
* do so will trigger a revert.
*
* `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the
* `tokens` array. This array must match the Pool's registered tokens.
*
* This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement
* their own custom logic. This typically requires additional information from the user (such as the expected number
* of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and
* passed directly to the Pool's contract.
*
* Emits a `PoolBalanceChanged` event.
*/
function exitPool(
bytes32 poolId,
address sender,
address payable recipient,
ExitPoolRequest calldata request
) external;
/// @dev modified to address[]
struct ExitPoolRequest {
address[] assets;
uint[] minAmountsOut;
bytes userData;
bool toInternalBalance;
}
/**
* @dev Emitted when a user joins or exits a Pool by calling `joinPool` or `exitPool`, respectively.
*/
event PoolBalanceChanged(
bytes32 indexed poolId,
address indexed liquidityProvider,
IERC20[] tokens,
int[] deltas,
uint[] protocolFeeAmounts
);
enum PoolBalanceChangeKind {
JOIN,
EXIT
}
// Swaps
//
// Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,
// they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be
// aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.
//
// The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.
// In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),
// and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').
// More complex swaps, such as one token in to multiple tokens out can be achieved by batching together
// individual swaps.
//
// There are two swap kinds:
// - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the
// `onSwap` hook) the amount of tokens out (to send to the recipient).
// - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines
// (via the `onSwap` hook) the amount of tokens in (to receive from the sender).
//
// Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with
// the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated
// tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended
// swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at
// the final intended token.
//
// In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal
// Balance) after all individual swaps have been completed, and the net token balance change computed. This makes
// certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost
// much less gas than they would otherwise.
//
// It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple
// Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only
// updating the Pool's internal accounting).
//
// To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token
// involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the
// minimum amount of tokens to receive (by passing a negative value) is specified.
//
// Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after
// this point in time (e.g. if the transaction failed to be included in a block promptly).
//
// If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do
// the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be
// passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the
// same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).
//
// Finally, Internal Balance can be used when either sending or receiving tokens.
enum SwapKind {
GIVEN_IN,
GIVEN_OUT
}
/**
* @dev Performs a swap with a single Pool.
*
* If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens
* taken from the Pool, which must be greater than or equal to `limit`.
*
* If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens
* sent to the Pool, which must be less than or equal to `limit`.
*
* Internal Balance usage and the recipient are determined by the `funds` struct.
*
* Emits a `Swap` event.
*/
function swap(
SingleSwap calldata singleSwap,
FundManagement calldata funds,
uint limit,
uint deadline
) external payable returns (uint);
/**
* @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on
* the `kind` value.
*
* `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).
* Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.
*
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
* used to extend swap behavior.
*/
struct SingleSwap {
bytes32 poolId;
SwapKind kind;
IAsset assetIn;
IAsset assetOut;
uint amount;
bytes userData;
}
/**
* @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either
* the amount of tokens sent to or received from the Pool, depending on the `kind` value.
*
* Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the
* Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at
* the same index in the `assets` array.
*
* Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a
* Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or
* `amountOut` depending on the swap kind.
*
* Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out
* of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal
* the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.
*
* The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,
* or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and
* out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to
* or unwrapped from WETH by the Vault.
*
* Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies
* the minimum or maximum amount of each token the vault is allowed to transfer.
*
* `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the
* equivalent `swap` call.
*
* Emits `Swap` events.
*/
function batchSwap(
SwapKind kind,
BatchSwapStep[] calldata swaps,
IAsset[] calldata assets,
FundManagement calldata funds,
int[] calldata limits,
uint deadline
) external payable returns (int[] memory);
/**
* @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the
* `assets` array passed to that function, and ETH assets are converted to WETH.
*
* If `amount` is zero, the multihop mechanism is used to determine the actual amount based on the amount in/out
* from the previous swap, depending on the swap kind.
*
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
* used to extend swap behavior.
*/
struct BatchSwapStep {
bytes32 poolId;
uint assetInIndex;
uint assetOutIndex;
uint amount;
bytes userData;
}
/**
* @dev Emitted for each individual swap performed by `swap` or `batchSwap`.
*/
event Swap(bytes32 indexed poolId, IERC20 indexed tokenIn, IERC20 indexed tokenOut, uint amountIn, uint amountOut);
/**
* @dev All tokens in a swap are either sent from the `sender` account to the Vault, or from the Vault to the
* `recipient` account.
*
* If the caller is not `sender`, it must be an authorized relayer for them.
*
* If `fromInternalBalance` is true, the `sender`'s Internal Balance will be preferred, performing an ERC20
* transfer for the difference between the requested amount and the User's Internal Balance (if any). The `sender`
* must have allowed the Vault to use their tokens via `IERC20.approve()`. This matches the behavior of
* `joinPool`.
*
* If `toInternalBalance` is true, tokens will be deposited to `recipient`'s internal balance instead of
* transferred. This matches the behavior of `exitPool`.
*
* Note that ETH cannot be deposited to or withdrawn from Internal Balance: attempting to do so will trigger a
* revert.
*/
struct FundManagement {
address sender;
bool fromInternalBalance;
address payable recipient;
bool toInternalBalance;
}
/**
* @dev Simulates a call to `batchSwap`, returning an array of Vault asset deltas. Calls to `swap` cannot be
* simulated directly, but an equivalent `batchSwap` call can and will yield the exact same result.
*
* Each element in the array corresponds to the asset at the same index, and indicates the number of tokens (or ETH)
* the Vault would take from the sender (if positive) or send to the recipient (if negative). The arguments it
* receives are the same that an equivalent `batchSwap` call would receive.
*
* Unlike `batchSwap`, this function performs no checks on the sender or recipient field in the `funds` struct.
* This makes it suitable to be called by off-chain applications via eth_call without needing to hold tokens,
* approve them for the Vault, or even know a user's address.
*
* Note that this function is not 'view' (due to implementation details): the client code must explicitly execute
* eth_call instead of eth_sendTransaction.
*/
function queryBatchSwap(
SwapKind kind,
BatchSwapStep[] calldata swaps,
IAsset[] calldata assets,
FundManagement calldata funds
) external returns (int[] memory assetDeltas);
// BasePool.sol
/**
* @dev Returns the amount of BPT that would be burned from `sender` if the `onExitPool` hook were called by the
* Vault with the same arguments, along with the number of tokens `recipient` would receive.
*
* This function is not meant to be called directly, but rather from a helper contract that fetches current Vault
* data, such as the protocol swap fee percentage and Pool balances.
*
* Like `IVault.queryBatchSwap`, this function is not view due to internal implementation details: the caller must
* explicitly use eth_call instead of eth_sendTransaction.
*/
function queryExit(
bytes32 poolId,
address sender,
address recipient,
uint[] memory balances,
uint lastChangeBlock,
uint protocolSwapFeePercentage,
bytes memory userData
) external returns (uint bptIn, uint[] memory amountsOut);
}
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;
interface IBalancerHelper {
function queryExit(
bytes32 poolId,
address sender,
address recipient,
IVault.JoinPoolRequest memory request
) external returns (uint bptIn, uint[] memory amountsOut);
function queryJoin(
bytes32 poolId,
address sender,
address recipient,
IVault.JoinPoolRequest memory request
) external returns (uint bptOut, uint[] memory amountsIn);
function vault() external view returns (address);
}
interface IVault {
struct JoinPoolRequest {
address[] assets;
uint[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/// @dev Get price, swap, liquidity calculations. Used by strategies and swapper
/// @author Alien Deployer (https://github.com/a17)
/// @author JodsMigel (https://github.com/JodsMigel)
interface IAmmAdapter is IERC165 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error PriceIncreased();
error WrongCallbackAmount();
error NotSupportedByCAMM();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
event SwapInPool(
address pool,
address tokenIn,
address tokenOut,
address recipient,
uint priceImpactTolerance,
uint amountIn,
uint amountOut
);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DATA TYPES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct SwapCallbackData {
address tokenIn;
uint amount;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* VIEW FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice String ID of the adapter
function ammAdapterId() external view returns (string memory);
/// @notice Tokens of a pool supported by the adapter
function poolTokens(address pool) external view returns (address[] memory);
/// @notice Computes the maximum amount of liquidity received for given amounts of pool assets and the current
/// pool price.
/// This function signature can be used only for non-concentrated AMMs.
/// @param pool Address of a pool supported by the adapter
/// @param amounts Amounts of pool assets
/// @return liquidity Liquidity out value
/// @return amountsConsumed Amounts of consumed assets when providing liquidity
function getLiquidityForAmounts(
address pool,
uint[] memory amounts
) external view returns (uint liquidity, uint[] memory amountsConsumed);
/// @notice Priced proportions of pool assets
/// @param pool Address of a pool supported by the adapter
/// @return Proportions with 18 decimals precision. Max is 1e18, min is 0.
function getProportions(address pool) external view returns (uint[] memory);
/// @notice Current price in pool without amount impact
/// @param pool Address of a pool supported by the adapter
/// @param tokenIn Token for sell
/// @param tokenOut Token for buy
/// @param amount Amount of tokenIn. For zero value provided amount 1.0 (10 ** decimals of tokenIn) will be used.
/// @return Amount of tokenOut with tokenOut decimals precision
function getPrice(address pool, address tokenIn, address tokenOut, uint amount) external view returns (uint);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WRITE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Swap given tokenIn for tokenOut. Assume that tokenIn already sent to this contract.
/// @param pool Address of a pool supported by the adapter
/// @param tokenIn Token for sell
/// @param tokenOut Token for buy
/// @param recipient Recipient for tokenOut
/// @param priceImpactTolerance Price impact tolerance. Must include fees at least. Denominator is 100_000.
function swap(
address pool,
address tokenIn,
address tokenOut,
address recipient,
uint priceImpactTolerance
) external;
/// @dev Initializer for proxied adapter
function init(address platform) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
/// @dev Base core interface implemented by most platform contracts.
/// Inherited contracts store an immutable Platform proxy address in the storage,
/// which provides authorization capabilities and infrastructure contract addresses.
/// @author Alien Deployer (https://github.com/a17)
/// @author JodsMigel (https://github.com/JodsMigel)
interface IControllable {
//region ----- Custom Errors -----
error IncorrectZeroArgument();
error IncorrectMsgSender();
error NotGovernance();
error NotMultisig();
error NotGovernanceAndNotMultisig();
error NotOperator();
error NotFactory();
error NotPlatform();
error NotVault();
error IncorrectArrayLength();
error AlreadyExist();
error NotExist();
error NotTheOwner();
error ETHTransferFailed();
error IncorrectInitParams();
//endregion -- Custom Errors -----
event ContractInitialized(address platform, uint ts, uint block);
/// @notice Stability Platform main contract address
function platform() external view returns (address);
/// @notice Version of contract implementation
/// @dev SemVer scheme MAJOR.MINOR.PATCH
//slither-disable-next-line naming-convention
function VERSION() external view returns (string memory);
/// @notice Block number when contract was initialized
function createdBlock() external view returns (uint);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
interface IBalancerAdapter {
/// @dev Add BalancerHelpers contract address
function setupHelpers(address balancerHelpers) external;
/// @notice Computes the maximum amount of liquidity received for given amounts of pool assets and the current
/// pool price.
/// This function signature can be used only for non-concentrated AMMs.
/// @dev This method used instead getLiquidityForAmounts because BalancerHelpers use queryJoin
/// write method. Can be used off-chain by callStatic.
/// @param pool Address of a pool supported by the adapter
/// @param amounts Amounts of pool assets
/// @return liquidity Liquidity out value
/// @return amountsConsumed Amounts of consumed assets when providing liquidity
function getLiquidityForAmountsWrite(
address pool,
uint[] memory amounts
) external returns (uint liquidity, uint[] memory amountsConsumed);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
/// @title Minimal library for setting / getting slot variables (used in upgradable proxy contracts)
library SlotsLib {
/// @dev Gets a slot as an address
function getAddress(bytes32 slot) internal view returns (address result) {
assembly {
result := sload(slot)
}
}
/// @dev Gets a slot as uint256
function getUint(bytes32 slot) internal view returns (uint result) {
assembly {
result := sload(slot)
}
}
/// @dev Sets a slot with address
/// @notice Check address for 0 at the setter
function set(bytes32 slot, address value) internal {
assembly {
sstore(slot, value)
}
}
/// @dev Sets a slot with uint
function set(bytes32 slot, uint value) internal {
assembly {
sstore(slot, value)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
/// @notice Interface of the main contract and entry point to the platform.
/// @author Alien Deployer (https://github.com/a17)
/// @author Jude (https://github.com/iammrjude)
/// @author JodsMigel (https://github.com/JodsMigel)
interface IPlatform {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error AlreadyAnnounced();
error SameVersion();
error NoNewVersion();
error UpgradeTimerIsNotOver(uint TimerTimestamp);
error IncorrectFee(uint minFee, uint maxFee);
error NotEnoughAllowedBBToken();
error TokenAlreadyExistsInSet(address token);
error AggregatorNotExists(address dexAggRouter);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
event PlatformVersion(string version);
event UpgradeAnnounce(
string oldVersion, string newVersion, address[] proxies, address[] newImplementations, uint timelock
);
event CancelUpgrade(string oldVersion, string newVersion);
event ProxyUpgraded(
address indexed proxy, address implementation, string oldContractVersion, string newContractVersion
);
event Addresses(
address multisig_,
address factory_,
address priceReader_,
address swapper_,
address buildingPermitToken_,
address vaultManager_,
address strategyLogic_,
address aprOracle_,
address hardWorker,
address rebalancer,
address zap,
address bridge
);
event OperatorAdded(address operator);
event OperatorRemoved(address operator);
event FeesChanged(uint fee, uint feeShareVaultManager, uint feeShareStrategyLogic, uint feeShareEcosystem);
event MinInitialBoostChanged(uint minInitialBoostPerDay, uint minInitialBoostDuration);
event NewAmmAdapter(string id, address proxy);
event EcosystemRevenueReceiver(address receiver);
event SetAllowedBBTokenVaults(address bbToken, uint vaultsToBuild, bool firstSet);
event RemoveAllowedBBToken(address bbToken);
event AddAllowedBoostRewardToken(address token);
event RemoveAllowedBoostRewardToken(address token);
event AddDefaultBoostRewardToken(address token);
event RemoveDefaultBoostRewardToken(address token);
event AddBoostTokens(address[] allowedBoostRewardToken, address[] defaultBoostRewardToken);
event AllowedBBTokenVaultUsed(address bbToken, uint vaultToUse);
event AddDexAggregator(address router);
event RemoveDexAggregator(address router);
event MinTvlForFreeHardWorkChanged(uint oldValue, uint newValue);
event CustomVaultFee(address vault, uint platformFee);
event Rebalancer(address rebalancer_);
event Bridge(address bridge_);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DATA TYPES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct PlatformUpgrade {
string newVersion;
address[] proxies;
address[] newImplementations;
}
struct PlatformSettings {
string networkName;
bytes32 networkExtra;
uint fee;
uint feeShareVaultManager;
uint feeShareStrategyLogic;
uint feeShareEcosystem;
uint minInitialBoostPerDay;
uint minInitialBoostDuration;
}
struct AmmAdapter {
string id;
address proxy;
}
struct SetupAddresses {
address factory;
address priceReader;
address swapper;
address buildingPermitToken;
address buildingPayPerVaultToken;
address vaultManager;
address strategyLogic;
address aprOracle;
address targetExchangeAsset;
address hardWorker;
address zap;
address bridge;
address rebalancer;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* VIEW FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Platform version in CalVer scheme: YY.MM.MINOR-tag. Updates on core contract upgrades.
function platformVersion() external view returns (string memory);
/// @notice Time delay for proxy upgrades of core contracts and changing important platform settings by multisig
//slither-disable-next-line naming-convention
function TIME_LOCK() external view returns (uint);
/// @notice DAO governance
function governance() external view returns (address);
/// @notice Core team multi signature wallet. Development and operations fund
function multisig() external view returns (address);
/// @notice This NFT allow user to build limited number of vaults per week
function buildingPermitToken() external view returns (address);
/// @notice This ERC20 token is used as payment token for vault building
function buildingPayPerVaultToken() external view returns (address);
/// @notice Receiver of ecosystem revenue
function ecosystemRevenueReceiver() external view returns (address);
/// @dev The best asset in a network for swaps between strategy assets and farms rewards assets
/// The target exchange asset is used for finding the best strategy's exchange asset.
/// Rhe fewer routes needed to swap to the target exchange asset, the better.
function targetExchangeAsset() external view returns (address);
/// @notice Platform factory assembling vaults. Stores settings, strategy logic, farms.
/// Provides the opportunity to upgrade vaults and strategies.
/// @return Address of Factory proxy
function factory() external view returns (address);
/// @notice The holders of these NFT receive a share of the vault revenue
/// @return Address of VaultManager proxy
function vaultManager() external view returns (address);
/// @notice The holders of these tokens receive a share of the revenue received in all vaults using this strategy logic.
function strategyLogic() external view returns (address);
/// @notice Combining oracle and DeX spot prices
/// @return Address of PriceReader proxy
function priceReader() external view returns (address);
/// @notice Providing underlying assets APRs on-chain
/// @return Address of AprOracle proxy
function aprOracle() external view returns (address);
/// @notice On-chain price quoter and swapper
/// @return Address of Swapper proxy
function swapper() external view returns (address);
/// @notice HardWork resolver and caller
/// @return Address of HardWorker proxy
function hardWorker() external view returns (address);
/// @notice Rebalance resolver
/// @return Address of Rebalancer proxy
function rebalancer() external view returns (address);
/// @notice ZAP feature
/// @return Address of Zap proxy
function zap() external view returns (address);
/// @notice Stability Bridge
/// @return Address of Bridge proxy
function bridge() external view returns (address);
/// @notice Name of current EVM network
function networkName() external view returns (string memory);
/// @notice Minimal initial boost rewards per day USD amount which needs to create rewarding vault
function minInitialBoostPerDay() external view returns (uint);
/// @notice Minimal boost rewards vesting duration for initial boost
function minInitialBoostDuration() external view returns (uint);
/// @notice This function provides the timestamp of the platform upgrade timelock.
/// @dev This function is an external view function, meaning it doesn't modify the state.
/// @return uint representing the timestamp of the platform upgrade timelock.
function platformUpgradeTimelock() external view returns (uint);
/// @dev Extra network data
/// @return 0-2 bytes - color
/// 3-5 bytes - background color
/// 6-31 bytes - free
function networkExtra() external view returns (bytes32);
/// @notice Pending platform upgrade data
function pendingPlatformUpgrade() external view returns (PlatformUpgrade memory);
/// @notice Get platform revenue fee settings
/// @return fee Revenue fee % (between MIN_FEE - MAX_FEE) with DENOMINATOR precision.
/// @return feeShareVaultManager Revenue fee share % of VaultManager tokenId owner
/// @return feeShareStrategyLogic Revenue fee share % of StrategyLogic tokenId owner
/// @return feeShareEcosystem Revenue fee share % of ecosystemFeeReceiver
function getFees()
external
view
returns (uint fee, uint feeShareVaultManager, uint feeShareStrategyLogic, uint feeShareEcosystem);
/// @notice Get custom vault platform fee
/// @return fee revenue fee % with DENOMINATOR precision
function getCustomVaultFee(address vault) external view returns (uint fee);
/// @notice Platform settings
function getPlatformSettings() external view returns (PlatformSettings memory);
/// @notice AMM adapters of the platform
function getAmmAdapters() external view returns (string[] memory id, address[] memory proxy);
/// @notice Get AMM adapter data by hash
/// @param ammAdapterIdHash Keccak256 hash of adapter ID string
/// @return ID string and proxy address of AMM adapter
function ammAdapter(bytes32 ammAdapterIdHash) external view returns (AmmAdapter memory);
/// @notice Allowed buy-back tokens for rewarding vaults
function allowedBBTokens() external view returns (address[] memory);
/// @notice Vaults building limit for buy-back token.
/// This limit decrements when a vault for BB-token is built.
/// @param token Allowed buy-back token
/// @return vaultsLimit Number of vaults that can be built for BB-token
function allowedBBTokenVaults(address token) external view returns (uint vaultsLimit);
/// @notice Vaults building limits for allowed buy-back tokens.
/// @return bbToken Allowed buy-back tokens
/// @return vaultsLimit Number of vaults that can be built for BB-tokens
function allowedBBTokenVaults() external view returns (address[] memory bbToken, uint[] memory vaultsLimit);
/// @notice Non-zero vaults building limits for allowed buy-back tokens.
/// @return bbToken Allowed buy-back tokens
/// @return vaultsLimit Number of vaults that can be built for BB-tokens
function allowedBBTokenVaultsFiltered()
external
view
returns (address[] memory bbToken, uint[] memory vaultsLimit);
/// @notice Check address for existance in operators list
/// @param operator Address
/// @return True if this address is Stability Operator
function isOperator(address operator) external view returns (bool);
/// @notice Tokens that can be used for boost rewards of rewarding vaults
/// @return Addresses of tokens
function allowedBoostRewardTokens() external view returns (address[] memory);
/// @notice Allowed boost reward tokens that used for unmanaged rewarding vaults creation
/// @return Addresses of tokens
function defaultBoostRewardTokens() external view returns (address[] memory);
/// @notice Allowed boost reward tokens that used for unmanaged rewarding vaults creation
/// @param addressToRemove This address will be removed from default boost reward tokens
/// @return Addresses of tokens
function defaultBoostRewardTokensFiltered(address addressToRemove) external view returns (address[] memory);
/// @notice Allowed DeX aggregators
/// @return Addresses of DeX aggregator rounters
function dexAggregators() external view returns (address[] memory);
/// @notice DeX aggregator router address is allowed to be used in the platform
/// @param dexAggRouter Address of DeX aggreagator router
/// @return Can be used
function isAllowedDexAggregatorRouter(address dexAggRouter) external view returns (bool);
/// @notice Show minimum TVL for compensate if vault has not enough ETH
/// @return Minimum TVL for compensate.
function minTvlForFreeHardWork() external view returns (uint);
/// @notice Front-end platform viewer
/// @return platformAddresses Platform core addresses
/// platformAddresses[0] factory
/// platformAddresses[1] vaultManager
/// platformAddresses[2] strategyLogic
/// platformAddresses[3] buildingPermitToken
/// platformAddresses[4] buildingPayPerVaultToken
/// platformAddresses[5] governance
/// platformAddresses[6] multisig
/// platformAddresses[7] zap
/// platformAddresses[8] bridge
/// @return bcAssets Blue chip token addresses
/// @return dexAggregators_ DeX aggregators allowed to be used entire the platform
/// @return vaultType Vault type ID strings
/// @return vaultExtra Vault color, background color and other extra data. Index of vault same as in previous array.
/// @return vaultBulldingPrice Price of creating new vault in buildingPayPerVaultToken. Index of vault same as in previous array.
/// @return strategyId Strategy logic ID strings
/// @return isFarmingStrategy True if strategy is farming strategy. Index of strategy same as in previous array.
/// @return strategyTokenURI StrategyLogic NFT tokenId metadata and on-chain image. Index of strategy same as in previous array.
/// @return strategyExtra Strategy color, background color and other extra data. Index of strategy same as in previous array.
function getData()
external
view
returns (
address[] memory platformAddresses,
address[] memory bcAssets,
address[] memory dexAggregators_,
string[] memory vaultType,
bytes32[] memory vaultExtra,
uint[] memory vaultBulldingPrice,
string[] memory strategyId,
bool[] memory isFarmingStrategy,
string[] memory strategyTokenURI,
bytes32[] memory strategyExtra
);
// todo add vaultSymbol, vaultName
/// @notice Front-end balances, prices and vault list viewer
/// @param yourAccount Address of account to query balances
/// @return token Tokens supported by the platform
/// @return tokenPrice USD price of token. Index of token same as in previous array.
/// @return tokenUserBalance User balance of token. Index of token same as in previous array.
/// @return vault Deployed vaults
/// @return vaultSharePrice Price 1.0 vault share. Index of vault same as in previous array.
/// @return vaultUserBalance User balance of vault. Index of vault same as in previous array.
/// @return nft Ecosystem NFTs
/// nft[0] BuildingPermitToken
/// nft[1] VaultManager
/// nft[2] StrategyLogic
/// @return nftUserBalance User balance of NFT. Index of NFT same as in previous array.
/// @return buildingPayPerVaultTokenBalance User balance of vault creation paying token
function getBalance(address yourAccount)
external
view
returns (
address[] memory token,
uint[] memory tokenPrice,
uint[] memory tokenUserBalance,
address[] memory vault,
uint[] memory vaultSharePrice,
uint[] memory vaultUserBalance,
address[] memory nft,
uint[] memory nftUserBalance,
uint buildingPayPerVaultTokenBalance
);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WRITE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Add platform operator.
/// Only governance and multisig can add operator.
/// @param operator Address of new operator
function addOperator(address operator) external;
/// @notice Remove platform operator.
/// Only governance and multisig can remove operator.
/// @param operator Address of operator to remove
function removeOperator(address operator) external;
/// @notice Announce upgrade of platform proxies implementations
/// Only governance and multisig can announce platform upgrades.
/// @param newVersion New platform version. Version must be changed when upgrading.
/// @param proxies Addresses of core contract proxies
/// @param newImplementations New implementation for proxy. Index of proxy same as in previous array.
function announcePlatformUpgrade(
string memory newVersion,
address[] memory proxies,
address[] memory newImplementations
) external;
/// @notice Upgrade platform
/// Only operator (multisig is operator too) can ececute pending platform upgrade
function upgrade() external;
/// @notice Cancel pending platform upgrade
/// Only operator (multisig is operator too) can ececute pending platform upgrade
function cancelUpgrade() external;
/// @notice Register AMM adapter in platform
/// @param id AMM adapter ID string from AmmAdapterIdLib
/// @param proxy Address of AMM adapter proxy
function addAmmAdapter(string memory id, address proxy) external;
// todo Only governance and multisig can set allowed bb-token vaults building limit
/// @notice Set new vaults building limit for buy-back token
/// @param bbToken Address of allowed buy-back token
/// @param vaultsToBuild Number of vaults that can be built for BB-token
function setAllowedBBTokenVaults(address bbToken, uint vaultsToBuild) external;
// todo Only governance and multisig can add allowed boost reward token
/// @notice Add new allowed boost reward token
/// @param token Address of token
function addAllowedBoostRewardToken(address token) external;
// todo Only governance and multisig can remove allowed boost reward token
/// @notice Remove allowed boost reward token
/// @param token Address of allowed boost reward token
function removeAllowedBoostRewardToken(address token) external;
// todo Only governance and multisig can add default boost reward token
/// @notice Add default boost reward token
/// @param token Address of default boost reward token
function addDefaultBoostRewardToken(address token) external;
// todo Only governance and multisig can remove default boost reward token
/// @notice Remove default boost reward token
/// @param token Address of allowed boost reward token
function removeDefaultBoostRewardToken(address token) external;
// todo Only governance and multisig can add allowed boost reward token
// todo Only governance and multisig can add default boost reward token
/// @notice Add new allowed boost reward token
/// @notice Add default boost reward token
/// @param allowedBoostRewardToken Address of allowed boost reward token
/// @param defaultBoostRewardToken Address of default boost reward token
function addBoostTokens(
address[] memory allowedBoostRewardToken,
address[] memory defaultBoostRewardToken
) external;
/// @notice Decrease allowed BB-token vault building limit when vault is built
/// Only Factory can do it.
/// @param bbToken Address of allowed buy-back token
function useAllowedBBTokenVault(address bbToken) external;
/// @notice Allow DeX aggregator routers to be used in the platform
/// @param dexAggRouter Addresses of DeX aggreagator routers
function addDexAggregators(address[] memory dexAggRouter) external;
/// @notice Remove allowed DeX aggregator router from the platform
/// @param dexAggRouter Address of DeX aggreagator router
function removeDexAggregator(address dexAggRouter) external;
/// @notice Change initial boost rewards settings
/// @param minInitialBoostPerDay_ Minimal initial boost rewards per day USD amount which needs to create rewarding vault
/// @param minInitialBoostDuration_ Minimal boost rewards vesting duration for initial boost
function setInitialBoost(uint minInitialBoostPerDay_, uint minInitialBoostDuration_) external;
/// @notice Update new minimum TVL for compensate.
/// @param value New minimum TVL for compensate.
function setMinTvlForFreeHardWork(uint value) external;
/// @notice Set custom platform fee for vault
/// @param vault Vault address
/// @param platformFee Custom platform fee
function setCustomVaultFee(address vault, uint platformFee) external;
/// @notice Setup Rebalancer.
/// Only Goverannce or Multisig can do this when Rebalancer is not set.
/// @param rebalancer_ Proxy address of Bridge
function setupRebalancer(address rebalancer_) external;
/// @notice Setup Bridge.
/// Only Goverannce or Multisig can do this when Bridge is not set.
/// @param bridge_ Proxy address of Bridge
function setupBridge(address bridge_) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "./BalancerErrors.sol";
/**
* @dev Exponentiation and logarithm functions for 18 decimal fixed point numbers (both base and exponent/argument).
*
* Exponentiation and logarithm with arbitrary bases (x^y and log_x(y)) are implemented by conversion to natural
* exponentiation and logarithm (where the base is Euler's number).
*
* @author Fernando Martinelli - @fernandomartinelli
* @author Sergio Yuhjtman - @sergioyuhjtman
* @author Daniel Fernandez - @dmf7z
*/
library LogExpMath {
// All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying
// two numbers, and multiply by ONE when dividing them.
// All arguments and return values are 18 decimal fixed point numbers.
int constant ONE_18 = 1e18;
// Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the
// case of ln36, 36 decimals.
int constant ONE_20 = 1e20;
int constant ONE_36 = 1e36;
// The domain of natural exponentiation is bound by the word size and number of decimals used.
//
// Because internally the result will be stored using 20 decimals, the largest possible result is
// (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221.
// The smallest possible result is 10^(-18), which makes largest negative argument
// ln(10^(-18)) = -41.446531673892822312.
// We use 130.0 and -41.0 to have some safety margin.
int constant MAX_NATURAL_EXPONENT = 130e18;
int constant MIN_NATURAL_EXPONENT = -41e18;
// Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point
// 256 bit integer.
int constant LN_36_LOWER_BOUND = ONE_18 - 1e17;
int constant LN_36_UPPER_BOUND = ONE_18 + 1e17;
uint constant MILD_EXPONENT_BOUND = 2 ** 254 / uint(ONE_20);
// 18 decimal constants
int constant x0 = 128000000000000000000; // 2ˆ7
int constant a0 = 38877084059945950922200000000000000000000000000000000000; // eˆ(x0) (no decimals)
int constant x1 = 64000000000000000000; // 2ˆ6
int constant a1 = 6235149080811616882910000000; // eˆ(x1) (no decimals)
// 20 decimal constants
int constant x2 = 3200000000000000000000; // 2ˆ5
int constant a2 = 7896296018268069516100000000000000; // eˆ(x2)
int constant x3 = 1600000000000000000000; // 2ˆ4
int constant a3 = 888611052050787263676000000; // eˆ(x3)
int constant x4 = 800000000000000000000; // 2ˆ3
int constant a4 = 298095798704172827474000; // eˆ(x4)
int constant x5 = 400000000000000000000; // 2ˆ2
int constant a5 = 5459815003314423907810; // eˆ(x5)
int constant x6 = 200000000000000000000; // 2ˆ1
int constant a6 = 738905609893065022723; // eˆ(x6)
int constant x7 = 100000000000000000000; // 2ˆ0
int constant a7 = 271828182845904523536; // eˆ(x7)
int constant x8 = 50000000000000000000; // 2ˆ-1
int constant a8 = 164872127070012814685; // eˆ(x8)
int constant x9 = 25000000000000000000; // 2ˆ-2
int constant a9 = 128402541668774148407; // eˆ(x9)
int constant x10 = 12500000000000000000; // 2ˆ-3
int constant a10 = 113314845306682631683; // eˆ(x10)
int constant x11 = 6250000000000000000; // 2ˆ-4
int constant a11 = 106449445891785942956; // eˆ(x11)
/**
* @dev Exponentiation (x^y) with unsigned 18 decimal fixed point base and exponent.
*
* Reverts if ln(x) * y is smaller than `MIN_NATURAL_EXPONENT`, or larger than `MAX_NATURAL_EXPONENT`.
*/
function pow(uint x, uint y) internal pure returns (uint) {
if (y == 0) {
// We solve the 0^0 indetermination by making it equal one.
return uint(ONE_18);
}
if (x == 0) {
return 0;
}
// Instead of computing x^y directly, we instead rely on the properties of logarithms and exponentiation to
// arrive at that result. In particular, exp(ln(x)) = x, and ln(x^y) = y * ln(x). This means
// x^y = exp(y * ln(x)).
// The ln function takes a signed value, so we need to make sure x fits in the signed 256 bit range.
_require(x < 2 ** 255, Errors.X_OUT_OF_BOUNDS);
int x_int256 = int(x);
// We will compute y * ln(x) in a single step. Depending on the value of x, we can either use ln or ln_36. In
// both cases, we leave the division by ONE_18 (due to fixed point multiplication) to the end.
// This prevents y * ln(x) from overflowing, and at the same time guarantees y fits in the signed 256 bit range.
_require(y < MILD_EXPONENT_BOUND, Errors.Y_OUT_OF_BOUNDS);
int y_int256 = int(y);
int logx_times_y;
if (LN_36_LOWER_BOUND < x_int256 && x_int256 < LN_36_UPPER_BOUND) {
int ln_36_x = _ln_36(x_int256);
// ln_36_x has 36 decimal places, so multiplying by y_int256 isn't as straightforward, since we can't just
// bring y_int256 to 36 decimal places, as it might overflow. Instead, we perform two 18 decimal
// multiplications and add the results: one with the first 18 decimals of ln_36_x, and one with the
// (downscaled) last 18 decimals.
logx_times_y = ((ln_36_x / ONE_18) * y_int256 + ((ln_36_x % ONE_18) * y_int256) / ONE_18);
} else {
logx_times_y = _ln(x_int256) * y_int256;
}
logx_times_y /= ONE_18;
// Finally, we compute exp(y * ln(x)) to arrive at x^y
_require(
MIN_NATURAL_EXPONENT <= logx_times_y && logx_times_y <= MAX_NATURAL_EXPONENT, Errors.PRODUCT_OUT_OF_BOUNDS
);
return uint(exp(logx_times_y));
}
/**
* @dev Natural exponentiation (e^x) with signed 18 decimal fixed point exponent.
*
* Reverts if `x` is smaller than MIN_NATURAL_EXPONENT, or larger than `MAX_NATURAL_EXPONENT`.
*/
function exp(int x) internal pure returns (int) {
_require(x >= MIN_NATURAL_EXPONENT && x <= MAX_NATURAL_EXPONENT, Errors.INVALID_EXPONENT);
if (x < 0) {
// We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it
// fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT).
// Fixed point division requires multiplying by ONE_18.
return ((ONE_18 * ONE_18) / exp(-x));
}
// First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n,
// where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7
// because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the
// decomposition.
// At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this
// decomposition, which will be lower than the smallest x_n.
// exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1.
// We mutate x by subtracting x_n, making it the remainder of the decomposition.
// The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause
// intermediate overflows. Instead we store them as plain integers, with 0 decimals.
// Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the
// decomposition.
// For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct
// it and compute the accumulated product.
int firstAN;
if (x >= x0) {
x -= x0;
firstAN = a0;
} else if (x >= x1) {
x -= x1;
firstAN = a1;
} else {
firstAN = 1; // One with no decimal places
}
// We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the
// smaller terms.
x *= 100;
// `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point
// one. Recall that fixed point multiplication requires dividing by ONE_20.
int product = ONE_20;
if (x >= x2) {
x -= x2;
product = (product * a2) / ONE_20;
}
if (x >= x3) {
x -= x3;
product = (product * a3) / ONE_20;
}
if (x >= x4) {
x -= x4;
product = (product * a4) / ONE_20;
}
if (x >= x5) {
x -= x5;
product = (product * a5) / ONE_20;
}
if (x >= x6) {
x -= x6;
product = (product * a6) / ONE_20;
}
if (x >= x7) {
x -= x7;
product = (product * a7) / ONE_20;
}
if (x >= x8) {
x -= x8;
product = (product * a8) / ONE_20;
}
if (x >= x9) {
x -= x9;
product = (product * a9) / ONE_20;
}
// x10 and x11 are unnecessary here since we have high enough precision already.
// Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series
// expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!).
int seriesSum = ONE_20; // The initial one in the sum, with 20 decimal places.
int term; // Each term in the sum, where the nth term is (x^n / n!).
// The first term is simply x.
term = x;
seriesSum += term;
// Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number,
// multiplying by it requires dividing by ONE_20, but dividing by the non-fixed point n values does not.
term = ((term * x) / ONE_20) / 2;
seriesSum += term;
term = ((term * x) / ONE_20) / 3;
seriesSum += term;
term = ((term * x) / ONE_20) / 4;
seriesSum += term;
term = ((term * x) / ONE_20) / 5;
seriesSum += term;
term = ((term * x) / ONE_20) / 6;
seriesSum += term;
term = ((term * x) / ONE_20) / 7;
seriesSum += term;
term = ((term * x) / ONE_20) / 8;
seriesSum += term;
term = ((term * x) / ONE_20) / 9;
seriesSum += term;
term = ((term * x) / ONE_20) / 10;
seriesSum += term;
term = ((term * x) / ONE_20) / 11;
seriesSum += term;
term = ((term * x) / ONE_20) / 12;
seriesSum += term;
// 12 Taylor terms are sufficient for 18 decimal precision.
// We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor
// approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply
// all three (one 20 decimal fixed point multiplication, dividing by ONE_20, and one integer multiplication),
// and then drop two digits to return an 18 decimal value.
return (((product * seriesSum) / ONE_20) * firstAN) / 100;
}
/**
* @dev Logarithm (log(arg, base), with signed 18 decimal fixed point base and argument.
*/
function log(int arg, int base) internal pure returns (int) {
// This performs a simple base change: log(arg, base) = ln(arg) / ln(base).
// Both logBase and logArg are computed as 36 decimal fixed point numbers, either by using ln_36, or by
// upscaling.
int logBase;
if (LN_36_LOWER_BOUND < base && base < LN_36_UPPER_BOUND) {
logBase = _ln_36(base);
} else {
logBase = _ln(base) * ONE_18;
}
int logArg;
if (LN_36_LOWER_BOUND < arg && arg < LN_36_UPPER_BOUND) {
logArg = _ln_36(arg);
} else {
logArg = _ln(arg) * ONE_18;
}
// When dividing, we multiply by ONE_18 to arrive at a result with 18 decimal places
return (logArg * ONE_18) / logBase;
}
/**
* @dev Natural logarithm (ln(a)) with signed 18 decimal fixed point argument.
*/
function ln(int a) internal pure returns (int) {
// The real natural logarithm is not defined for negative numbers or zero.
_require(a > 0, Errors.OUT_OF_BOUNDS);
if (LN_36_LOWER_BOUND < a && a < LN_36_UPPER_BOUND) {
return _ln_36(a) / ONE_18;
} else {
return _ln(a);
}
}
/**
* @dev Internal natural logarithm (ln(a)) with signed 18 decimal fixed point argument.
*/
function _ln(int a) private pure returns (int) {
if (a < ONE_18) {
// Since ln(a^k) = k * ln(a), we can compute ln(a) as ln(a) = ln((1/a)^(-1)) = - ln((1/a)). If a is less
// than one, 1/a will be greater than one, and this if statement will not be entered in the recursive call.
// Fixed point division requires multiplying by ONE_18.
return (-_ln((ONE_18 * ONE_18) / a));
}
// First, we use the fact that ln^(a * b) = ln(a) + ln(b) to decompose ln(a) into a sum of powers of two, which
// we call x_n, where x_n == 2^(7 - n), which are the natural logarithm of precomputed quantities a_n (that is,
// ln(a_n) = x_n). We choose the first x_n, x0, to equal 2^7 because the exponential of all larger powers cannot
// be represented as 18 fixed point decimal numbers in 256 bits, and are therefore larger than a.
// At the end of this process we will have the sum of all x_n = ln(a_n) that apply, and the remainder of this
// decomposition, which will be lower than the smallest a_n.
// ln(a) = k_0 * x_0 + k_1 * x_1 + ... + k_n * x_n + ln(remainder), where each k_n equals either 0 or 1.
// We mutate a by subtracting a_n, making it the remainder of the decomposition.
// For reasons related to how `exp` works, the first two a_n (e^(2^7) and e^(2^6)) are not stored as fixed point
// numbers with 18 decimals, but instead as plain integers with 0 decimals, so we need to multiply them by
// ONE_18 to convert them to fixed point.
// For each a_n, we test if that term is present in the decomposition (if a is larger than it), and if so divide
// by it and compute the accumulated sum.
int sum = 0;
if (a >= a0 * ONE_18) {
a /= a0; // Integer, not fixed point division
sum += x0;
}
if (a >= a1 * ONE_18) {
a /= a1; // Integer, not fixed point division
sum += x1;
}
// All other a_n and x_n are stored as 20 digit fixed point numbers, so we convert the sum and a to this format.
sum *= 100;
a *= 100;
// Because further a_n are 20 digit fixed point numbers, we multiply by ONE_20 when dividing by them.
if (a >= a2) {
a = (a * ONE_20) / a2;
sum += x2;
}
if (a >= a3) {
a = (a * ONE_20) / a3;
sum += x3;
}
if (a >= a4) {
a = (a * ONE_20) / a4;
sum += x4;
}
if (a >= a5) {
a = (a * ONE_20) / a5;
sum += x5;
}
if (a >= a6) {
a = (a * ONE_20) / a6;
sum += x6;
}
if (a >= a7) {
a = (a * ONE_20) / a7;
sum += x7;
}
if (a >= a8) {
a = (a * ONE_20) / a8;
sum += x8;
}
if (a >= a9) {
a = (a * ONE_20) / a9;
sum += x9;
}
if (a >= a10) {
a = (a * ONE_20) / a10;
sum += x10;
}
if (a >= a11) {
a = (a * ONE_20) / a11;
sum += x11;
}
// a is now a small number (smaller than a_11, which roughly equals 1.06). This means we can use a Taylor series
// that converges rapidly for values of `a` close to one - the same one used in ln_36.
// Let z = (a - 1) / (a + 1).
// ln(a) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))
// Recall that 20 digit fixed point division requires multiplying by ONE_20, and multiplication requires
// division by ONE_20.
int z = ((a - ONE_20) * ONE_20) / (a + ONE_20);
int z_squared = (z * z) / ONE_20;
// num is the numerator of the series: the z^(2 * n + 1) term
int num = z;
// seriesSum holds the accumulated sum of each term in the series, starting with the initial z
int seriesSum = num;
// In each step, the numerator is multiplied by z^2
num = (num * z_squared) / ONE_20;
seriesSum += num / 3;
num = (num * z_squared) / ONE_20;
seriesSum += num / 5;
num = (num * z_squared) / ONE_20;
seriesSum += num / 7;
num = (num * z_squared) / ONE_20;
seriesSum += num / 9;
num = (num * z_squared) / ONE_20;
seriesSum += num / 11;
// 6 Taylor terms are sufficient for 36 decimal precision.
// Finally, we multiply by 2 (non fixed point) to compute ln(remainder)
seriesSum *= 2;
// We now have the sum of all x_n present, and the Taylor approximation of the logarithm of the remainder (both
// with 20 decimals). All that remains is to sum these two, and then drop two digits to return a 18 decimal
// value.
return (sum + seriesSum) / 100;
}
/**
* @dev Intrnal high precision (36 decimal places) natural logarithm (ln(x)) with signed 18 decimal fixed point argument,
* for x close to one.
*
* Should only be used if x is between LN_36_LOWER_BOUND and LN_36_UPPER_BOUND.
*/
function _ln_36(int x) private pure returns (int) {
// Since ln(1) = 0, a value of x close to one will yield a very small result, which makes using 36 digits
// worthwhile.
// First, we transform x to a 36 digit fixed point value.
x *= ONE_18;
// We will use the following Taylor expansion, which converges very rapidly. Let z = (x - 1) / (x + 1).
// ln(x) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))
// Recall that 36 digit fixed point division requires multiplying by ONE_36, and multiplication requires
// division by ONE_36.
int z = ((x - ONE_36) * ONE_36) / (x + ONE_36);
int z_squared = (z * z) / ONE_36;
// num is the numerator of the series: the z^(2 * n + 1) term
int num = z;
// seriesSum holds the accumulated sum of each term in the series, starting with the initial z
int seriesSum = num;
// In each step, the numerator is multiplied by z^2
num = (num * z_squared) / ONE_36;
seriesSum += num / 3;
num = (num * z_squared) / ONE_36;
seriesSum += num / 5;
num = (num * z_squared) / ONE_36;
seriesSum += num / 7;
num = (num * z_squared) / ONE_36;
seriesSum += num / 9;
num = (num * z_squared) / ONE_36;
seriesSum += num / 11;
num = (num * z_squared) / ONE_36;
seriesSum += num / 13;
num = (num * z_squared) / ONE_36;
seriesSum += num / 15;
// 8 Taylor terms are sufficient for 36 decimal precision.
// All that remains is multiplying by 2 (non fixed point).
return seriesSum * 2;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
library LegacyOZMath {
function mul(uint a, uint b) internal pure returns (uint) {
return a * b;
}
function div(uint a, uint b, bool roundUp) internal pure returns (uint) {
return roundUp ? divUp(a, b) : divDown(a, b);
}
function divUp(uint a, uint b) internal pure returns (uint) {
if (a == 0) {
return 0;
} else {
return 1 + (a - 1) / b;
}
}
function divDown(uint a, uint b) internal pure returns (uint) {
return a / b;
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint a, uint b) internal pure returns (uint) {
return a > b ? a : b;
}
}