Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/AlgebraPool.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
pragma abicoder v1;
import './base/AlgebraPoolBase.sol';
import './base/ReentrancyGuard.sol';
import './base/Positions.sol';
import './base/SwapCalculation.sol';
import './base/ReservesManager.sol';
import './base/TickStructure.sol';
import './libraries/FullMath.sol';
import './libraries/Constants.sol';
import './libraries/SafeCast.sol';
import './libraries/TickMath.sol';
import './libraries/LiquidityMath.sol';
import './libraries/Plugins.sol';
import './interfaces/plugin/IAlgebraPlugin.sol';
import './interfaces/IAlgebraFactory.sol';
/// @title Algebra concentrated liquidity pool
/// @notice This contract is responsible for liquidity positions, swaps and flashloans
/// @dev Version: Algebra Integral 1.2.1
contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positions, SwapCalculation, ReservesManager {
using SafeCast for uint256;
using SafeCast for uint128;
using Plugins for uint8;
using Plugins for bytes4;
/// @inheritdoc IAlgebraPoolActions
function initialize(uint160 initialPrice) external override {
int24 tick = TickMath.getTickAtSqrtRatio(initialPrice); // getTickAtSqrtRatio checks validity of initialPrice inside
if (globalState.price != 0) revert alreadyInitialized(); // after initialization, the price can never become zero
globalState.price = initialPrice;
globalState.tick = tick;
emit Initialize(initialPrice, tick);
if (plugin != address(0)) {
IAlgebraPlugin(plugin).beforeInitialize(msg.sender, initialPrice).shouldReturn(IAlgebraPlugin.beforeInitialize.selector);
}
(uint16 _communityFee, int24 _tickSpacing, uint16 _fee) = _getDefaultConfiguration();
_setFee(_fee);
_setTickSpacing(_tickSpacing);
if (_communityFee != 0 && communityVault == address(0)) revert invalidNewCommunityFee(); // the pool should not accumulate a community fee without a vault
_setCommunityFee(_communityFee);
if (globalState.pluginConfig.hasFlag(Plugins.AFTER_INIT_FLAG)) {
IAlgebraPlugin(plugin).afterInitialize(msg.sender, initialPrice, tick).shouldReturn(IAlgebraPlugin.afterInitialize.selector);
}
}
/// @inheritdoc IAlgebraPoolActions
function mint(
address leftoversRecipient,
address recipient,
int24 bottomTick,
int24 topTick,
uint128 liquidityDesired,
bytes calldata data
) external override onlyValidTicks(bottomTick, topTick) returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) {
if (liquidityDesired == 0) revert zeroLiquidityDesired();
_beforeModifyPos(recipient, bottomTick, topTick, liquidityDesired.toInt128(), data);
_lock();
{
// scope to prevent stack too deep
int24 currentTick = globalState.tick;
uint160 currentPrice = globalState.price;
if (currentPrice == 0) revert notInitialized();
unchecked {
int24 _tickSpacing = tickSpacing;
if (bottomTick % _tickSpacing | topTick % _tickSpacing != 0) revert tickIsNotSpaced();
}
(amount0, amount1, ) = LiquidityMath.getAmountsForLiquidity(bottomTick, topTick, liquidityDesired.toInt128(), currentTick, currentPrice);
}
(uint256 receivedAmount0, uint256 receivedAmount1) = _updateReserves();
_mintCallback(amount0, amount1, data); // IAlgebraMintCallback.algebraMintCallback to msg.sender
receivedAmount0 = amount0 == 0 ? 0 : _balanceToken0() - receivedAmount0;
receivedAmount1 = amount1 == 0 ? 0 : _balanceToken1() - receivedAmount1;
if (receivedAmount0 < amount0) {
liquidityActual = uint128(FullMath.mulDiv(uint256(liquidityDesired), receivedAmount0, amount0));
} else {
liquidityActual = liquidityDesired;
}
if (receivedAmount1 < amount1) {
uint128 liquidityForRA1 = uint128(FullMath.mulDiv(uint256(liquidityDesired), receivedAmount1, amount1));
if (liquidityForRA1 < liquidityActual) liquidityActual = liquidityForRA1;
}
if (liquidityActual == 0) revert zeroLiquidityActual();
// scope to prevent "stack too deep"
{
Position storage _position = getOrCreatePosition(recipient, bottomTick, topTick);
(amount0, amount1) = _updatePositionTicksAndFees(_position, bottomTick, topTick, liquidityActual.toInt128());
}
unchecked {
// return leftovers
if (amount0 > 0) {
if (receivedAmount0 > amount0) _transfer(token0, leftoversRecipient, receivedAmount0 - amount0);
else assert(receivedAmount0 == amount0); // must always be true
}
if (amount1 > 0) {
if (receivedAmount1 > amount1) _transfer(token1, leftoversRecipient, receivedAmount1 - amount1);
else assert(receivedAmount1 == amount1); // must always be true
}
}
_changeReserves(int256(amount0), int256(amount1), 0, 0, 0, 0);
emit Mint(msg.sender, recipient, bottomTick, topTick, liquidityActual, amount0, amount1);
_unlock();
_afterModifyPos(recipient, bottomTick, topTick, liquidityActual.toInt128(), amount0, amount1, data);
}
/// @inheritdoc IAlgebraPoolActions
function burn(
int24 bottomTick,
int24 topTick,
uint128 amount,
bytes calldata data
) external override onlyValidTicks(bottomTick, topTick) returns (uint256 amount0, uint256 amount1) {
if (amount > uint128(type(int128).max)) revert arithmeticError();
int128 liquidityDelta = -int128(amount);
uint24 pluginFee = _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data);
_lock();
_updateReserves();
{
Position storage position = getOrCreatePosition(msg.sender, bottomTick, topTick);
(amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta);
if (pluginFee > 0) {
uint256 deltaPluginFeePending0;
uint256 deltaPluginFeePending1;
if (amount0 > 0) {
deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR);
amount0 -= deltaPluginFeePending0;
}
if (amount1 > 0) {
deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR);
amount1 -= deltaPluginFeePending1;
}
_changeReserves(0, 0, 0, 0, deltaPluginFeePending0, deltaPluginFeePending1);
}
if (amount0 | amount1 != 0) {
// since we do not support tokens whose total supply can exceed uint128, these casts are safe
// and, theoretically, unchecked cast prevents a complete blocking of burn
(position.fees0, position.fees1) = (position.fees0 + uint128(amount0), position.fees1 + uint128(amount1));
}
}
if (amount | amount0 | amount1 != 0) emit Burn(msg.sender, bottomTick, topTick, amount, amount0, amount1, pluginFee);
_unlock();
_afterModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, amount0, amount1, data);
}
function _isPlugin() internal view returns (bool) {
return msg.sender == plugin;
}
function _beforeModifyPos(
address owner,
int24 bottomTick,
int24 topTick,
int128 liquidityDelta,
bytes calldata data
) internal returns (uint24 pluginFee) {
if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_POSITION_MODIFY_FLAG)) {
if (_isPlugin()) return 0;
bytes4 selector;
(selector, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data);
if (pluginFee >= 1e6) revert incorrectPluginFee();
selector.shouldReturn(IAlgebraPlugin.beforeModifyPosition.selector);
}
}
function _afterModifyPos(address owner, int24 bTick, int24 tTick, int128 deltaL, uint256 amount0, uint256 amount1, bytes calldata data) internal {
if (_isPlugin()) return;
if (globalState.pluginConfig.hasFlag(Plugins.AFTER_POSITION_MODIFY_FLAG)) {
IAlgebraPlugin(plugin).afterModifyPosition(msg.sender, owner, bTick, tTick, deltaL, amount0, amount1, data).shouldReturn(
IAlgebraPlugin.afterModifyPosition.selector
);
}
}
/// @inheritdoc IAlgebraPoolActions
function collect(
address recipient,
int24 bottomTick,
int24 topTick,
uint128 amount0Requested,
uint128 amount1Requested
) external override returns (uint128 amount0, uint128 amount1) {
_lock();
// we don't check tick range validity, because if ticks are incorrect, the position will be empty
Position storage position = getOrCreatePosition(msg.sender, bottomTick, topTick);
(uint128 positionFees0, uint128 positionFees1) = (position.fees0, position.fees1);
if (amount0Requested > positionFees0) amount0Requested = positionFees0;
if (amount1Requested > positionFees1) amount1Requested = positionFees1;
if (amount0Requested | amount1Requested != 0) {
// use one if since fees0 and fees1 are tightly packed
(amount0, amount1) = (amount0Requested, amount1Requested);
unchecked {
// single SSTORE
(position.fees0, position.fees1) = (positionFees0 - amount0, positionFees1 - amount1);
if (amount0 > 0) _transfer(token0, recipient, amount0);
if (amount1 > 0) _transfer(token1, recipient, amount1);
_changeReserves(-int256(uint256(amount0)), -int256(uint256(amount1)), 0, 0, 0, 0);
}
emit Collect(msg.sender, recipient, bottomTick, topTick, amount0, amount1);
}
_unlock();
}
struct SwapEventParams {
uint160 currentPrice;
int24 currentTick;
uint128 currentLiquidity;
}
/// @inheritdoc IAlgebraPoolActions
function swap(
address recipient,
bool zeroToOne,
int256 amountRequired,
uint160 limitSqrtPrice,
bytes calldata data
) external override returns (int256 amount0, int256 amount1) {
(uint24 overrideFee, uint24 pluginFee) = _beforeSwap(recipient, zeroToOne, amountRequired, limitSqrtPrice, false, data);
_lock();
{
// scope to prevent "stack too deep"
SwapEventParams memory eventParams;
FeesAmount memory fees;
(amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, fees) = _calculateSwap(
overrideFee,
pluginFee,
zeroToOne,
amountRequired,
limitSqrtPrice
);
(uint256 balance0Before, uint256 balance1Before) = _updateReserves();
if (zeroToOne) {
unchecked {
if (amount1 < 0) _transfer(token1, recipient, uint256(-amount1)); // amount1 cannot be > 0
}
_swapCallback(amount0, amount1, data); // callback to get tokens from the msg.sender
if (balance0Before + uint256(amount0) > _balanceToken0()) revert insufficientInputAmount();
_changeReserves(amount0, amount1, fees.communityFeeAmount, 0, fees.pluginFeeAmount, 0); // reflect reserve change and pay communityFee
} else {
unchecked {
if (amount0 < 0) _transfer(token0, recipient, uint256(-amount0)); // amount0 cannot be > 0
}
_swapCallback(amount0, amount1, data); // callback to get tokens from the msg.sender
if (balance1Before + uint256(amount1) > _balanceToken1()) revert insufficientInputAmount();
_changeReserves(amount0, amount1, 0, fees.communityFeeAmount, 0, fees.pluginFeeAmount); // reflect reserve change and pay communityFee
}
_emitSwapEvent(
recipient,
amount0,
amount1,
eventParams.currentPrice,
eventParams.currentLiquidity,
eventParams.currentTick,
overrideFee,
pluginFee
);
}
_unlock();
_afterSwap(recipient, zeroToOne, amountRequired, limitSqrtPrice, amount0, amount1, data);
}
/// @inheritdoc IAlgebraPoolActions
function swapWithPaymentInAdvance(
address leftoversRecipient,
address recipient,
bool zeroToOne,
int256 amountToSell,
uint160 limitSqrtPrice,
bytes calldata data
) external override returns (int256 amount0, int256 amount1) {
if (amountToSell < 0) revert invalidAmountRequired(); // we support only exactInput here
_lock();
// firstly we are getting tokens from the original caller of the transaction
// since the pool can get less/more tokens then expected, _amountToSell_ can be changed
{
// scope to prevent "stack too deep"
int256 amountReceived;
if (zeroToOne) {
uint256 balanceBefore = _balanceToken0();
_swapCallback(amountToSell, 0, data); // callback to get tokens from the msg.sender
uint256 balanceAfter = _balanceToken0();
amountReceived = (balanceAfter - balanceBefore).toInt256();
_changeReserves(amountReceived, 0, 0, 0, 0, 0);
} else {
uint256 balanceBefore = _balanceToken1();
_swapCallback(0, amountToSell, data); // callback to get tokens from the msg.sender
uint256 balanceAfter = _balanceToken1();
amountReceived = (balanceAfter - balanceBefore).toInt256();
_changeReserves(0, amountReceived, 0, 0, 0, 0);
}
if (amountReceived != amountToSell) amountToSell = amountReceived;
}
if (amountToSell == 0) revert insufficientInputAmount();
_unlock();
(uint24 overrideFee, uint24 pluginFee) = _beforeSwap(recipient, zeroToOne, amountToSell, limitSqrtPrice, true, data);
_lock();
_updateReserves();
SwapEventParams memory eventParams;
FeesAmount memory fees;
(amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, fees) = _calculateSwap(
overrideFee,
pluginFee,
zeroToOne,
amountToSell,
limitSqrtPrice
);
unchecked {
// transfer to the recipient
if (zeroToOne) {
if (amount1 < 0) _transfer(token1, recipient, uint256(-amount1)); // amount1 cannot be > 0
uint256 leftover = uint256(amountToSell - amount0); // return the leftovers
if (leftover != 0) _transfer(token0, leftoversRecipient, leftover);
_changeReserves(-leftover.toInt256(), amount1, fees.communityFeeAmount, 0, fees.pluginFeeAmount, 0); // reflect reserve change and pay communityFee
} else {
if (amount0 < 0) _transfer(token0, recipient, uint256(-amount0)); // amount0 cannot be > 0
uint256 leftover = uint256(amountToSell - amount1); // return the leftovers
if (leftover != 0) _transfer(token1, leftoversRecipient, leftover);
_changeReserves(amount0, -leftover.toInt256(), 0, fees.communityFeeAmount, 0, fees.pluginFeeAmount); // reflect reserve change and pay communityFee
}
}
_emitSwapEvent(
recipient,
amount0,
amount1,
eventParams.currentPrice,
eventParams.currentLiquidity,
eventParams.currentTick,
overrideFee,
pluginFee
);
_unlock();
_afterSwap(recipient, zeroToOne, amountToSell, limitSqrtPrice, amount0, amount1, data);
}
/// @dev internal function to reduce bytecode size
function _emitSwapEvent(
address recipient,
int256 amount0,
int256 amount1,
uint160 newPrice,
uint128 newLiquidity,
int24 newTick,
uint24 overrideFee,
uint24 pluginFee
) private {
emit Swap(msg.sender, recipient, amount0, amount1, newPrice, newLiquidity, newTick, overrideFee, pluginFee);
}
function _beforeSwap(
address recipient,
bool zto,
int256 amount,
uint160 limitPrice,
bool payInAdvance,
bytes calldata data
) internal returns (uint24 overrideFee, uint24 pluginFee) {
uint8 pluginConfig = globalState.pluginConfig;
if (pluginConfig.hasFlag(Plugins.BEFORE_SWAP_FLAG)) {
if (_isPlugin()) return (0, 0);
bytes4 selector;
(selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data);
if (!pluginConfig.hasFlag(Plugins.DYNAMIC_FEE) && (overrideFee > 0 || pluginFee > 0)) revert dynamicFeeDisabled();
// we will check that fee is less than denominator inside the swap calculation
selector.shouldReturn(IAlgebraPlugin.beforeSwap.selector);
}
}
function _afterSwap(address recipient, bool zto, int256 amount, uint160 limitPrice, int256 amount0, int256 amount1, bytes calldata data) internal {
if (globalState.pluginConfig.hasFlag(Plugins.AFTER_SWAP_FLAG)) {
if (_isPlugin()) return;
IAlgebraPlugin(plugin).afterSwap(msg.sender, recipient, zto, amount, limitPrice, amount0, amount1, data).shouldReturn(
IAlgebraPlugin.afterSwap.selector
);
}
}
/// @inheritdoc IAlgebraPoolActions
function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external override {
if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_FLASH_FLAG)) {
IAlgebraPlugin(plugin).beforeFlash(msg.sender, recipient, amount0, amount1, data).shouldReturn(IAlgebraPlugin.beforeFlash.selector);
}
_lock();
uint256 paid0;
uint256 paid1;
{
(uint256 balance0Before, uint256 balance1Before) = _updateReserves();
uint256 fee0;
if (amount0 > 0) {
fee0 = FullMath.mulDivRoundingUp(amount0, Constants.FLASH_FEE, Constants.FEE_DENOMINATOR);
_transfer(token0, recipient, amount0);
}
uint256 fee1;
if (amount1 > 0) {
fee1 = FullMath.mulDivRoundingUp(amount1, Constants.FLASH_FEE, Constants.FEE_DENOMINATOR);
_transfer(token1, recipient, amount1);
}
_flashCallback(fee0, fee1, data); // IAlgebraFlashCallback.algebraFlashCallback to msg.sender
paid0 = _balanceToken0();
if (balance0Before + fee0 > paid0) revert flashInsufficientPaid0();
paid1 = _balanceToken1();
if (balance1Before + fee1 > paid1) revert flashInsufficientPaid1();
unchecked {
paid0 -= balance0Before;
paid1 -= balance1Before;
}
uint256 _communityFee = globalState.communityFee;
if (_communityFee > 0) {
uint256 communityFee0;
if (paid0 > 0) communityFee0 = FullMath.mulDiv(paid0, _communityFee, Constants.COMMUNITY_FEE_DENOMINATOR);
uint256 communityFee1;
if (paid1 > 0) communityFee1 = FullMath.mulDiv(paid1, _communityFee, Constants.COMMUNITY_FEE_DENOMINATOR);
_changeReserves(int256(communityFee0), int256(communityFee1), communityFee0, communityFee1, 0, 0);
}
emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1);
}
_unlock();
if (globalState.pluginConfig.hasFlag(Plugins.AFTER_FLASH_FLAG)) {
IAlgebraPlugin(plugin).afterFlash(msg.sender, recipient, amount0, amount1, paid0, paid1, data).shouldReturn(IAlgebraPlugin.afterFlash.selector);
}
}
/// @dev using function to save bytecode
function _checkIfAdministrator() private view {
if (!IAlgebraFactory(factory).hasRoleOrOwner(Constants.POOLS_ADMINISTRATOR_ROLE, msg.sender)) revert notAllowed();
}
// permissioned actions use reentrancy lock to prevent call from callback (to keep the correct order of events, etc.)
/// @inheritdoc IAlgebraPoolPermissionedActions
function setCommunityFee(uint16 newCommunityFee) external override onlyUnlocked {
_checkIfAdministrator();
if (
newCommunityFee > Constants.MAX_COMMUNITY_FEE ||
newCommunityFee == globalState.communityFee ||
(newCommunityFee != 0 && communityVault == address(0))
) revert invalidNewCommunityFee();
_setCommunityFee(newCommunityFee);
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function setTickSpacing(int24 newTickSpacing) external override onlyUnlocked {
_checkIfAdministrator();
if (newTickSpacing <= 0 || newTickSpacing > Constants.MAX_TICK_SPACING || tickSpacing == newTickSpacing) revert invalidNewTickSpacing();
_setTickSpacing(newTickSpacing);
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function setPlugin(address newPluginAddress) external override onlyUnlocked {
_checkIfAdministrator();
_setPluginConfig(0);
_setPlugin(newPluginAddress);
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function setPluginConfig(uint8 newConfig) external override onlyUnlocked {
address _plugin = plugin;
if (_plugin == address(0)) revert pluginIsNotConnected(); // it is not allowed to set plugin config without plugin
if (msg.sender != _plugin) _checkIfAdministrator();
_setPluginConfig(newConfig);
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function setCommunityVault(address newCommunityVault) external override onlyUnlocked {
// factory is allowed to set initial vault
if (msg.sender != factory) _checkIfAdministrator();
if (newCommunityVault == address(0) && globalState.communityFee != 0) _setCommunityFee(0); // the pool should not accumulate a community fee without a vault
_setCommunityFeeVault(newCommunityVault); // accumulated but not yet sent to the vault community fees once will be sent to the `newCommunityVault` address
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function setFee(uint16 newFee) external override {
_checkIfAdministrator();
bool isDynamicFeeEnabled = globalState.pluginConfig.hasFlag(Plugins.DYNAMIC_FEE);
if (!globalState.unlocked) revert locked(); // cheaper to check lock here
if (isDynamicFeeEnabled) revert dynamicFeeActive();
_setFee(newFee);
}
/// @dev using function to save bytecode
function _checkIfPlugin() private view {
if (msg.sender != plugin) revert notAllowed();
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function sync() external override {
_checkIfPlugin();
_lock();
_updateReserves();
_unlock();
}
/// @inheritdoc IAlgebraPoolPermissionedActions
function skim() external override {
_checkIfPlugin();
_lock();
_skimReserves(msg.sender);
_unlock();
}
}
"
},
"contracts/AlgebraPoolDeployer.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
pragma abicoder v1;
import './interfaces/IAlgebraPoolDeployer.sol';
import './AlgebraPool.sol';
/// @title Algebra pool deployer
/// @notice Is used by AlgebraFactory to deploy pools
/// @dev Version: Algebra Integral 1.2.1
contract AlgebraPoolDeployer is IAlgebraPoolDeployer {
/// @dev two storage slots for dense cache packing
bytes32 private cache0;
bytes32 private cache1;
address private immutable factory;
constructor(address _factory) {
require(_factory != address(0));
factory = _factory;
}
/// @inheritdoc IAlgebraPoolDeployer
function getDeployParameters() external view override returns (address _plugin, address _factory, address _token0, address _token1) {
(_plugin, _token0, _token1) = _readFromCache();
_factory = factory;
}
/// @inheritdoc IAlgebraPoolDeployer
function deploy(address plugin, address token0, address token1, address deployer) external override returns (address pool) {
require(msg.sender == factory);
_writeToCache(plugin, token0, token1);
bytes memory _encodedParams;
if (deployer == address(0)) {
_encodedParams = abi.encode(token0, token1);
} else {
_encodedParams = abi.encode(deployer, token0, token1);
}
pool = address(new AlgebraPool{salt: keccak256(_encodedParams)}());
(cache0, cache1) = (bytes32(0), bytes32(0));
}
/// @notice densely packs three addresses into two storage slots
function _writeToCache(address plugin, address token0, address token1) private {
assembly {
// cache0 = [plugin, token0[0, 96]], cache1 = [token0[0, 64], 0-s x32 , token1]
token0 := and(token0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) // clean higher bits, just in case
sstore(cache0.slot, or(shr(64, token0), shl(96, plugin)))
sstore(cache1.slot, or(shl(160, token0), and(token1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)))
}
}
/// @notice reads three densely packed addresses from two storage slots
function _readFromCache() private view returns (address plugin, address token0, address token1) {
(bytes32 _cache0, bytes32 _cache1) = (cache0, cache1);
assembly {
plugin := shr(96, _cache0)
token0 := or(shl(64, and(_cache0, 0xFFFFFFFFFFFFFFFFFFFFFFFF)), shr(160, _cache1))
token1 := and(_cache1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
}
"
},
"contracts/base/AlgebraPoolBase.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
import '../interfaces/callback/IAlgebraSwapCallback.sol';
import '../interfaces/callback/IAlgebraMintCallback.sol';
import '../interfaces/callback/IAlgebraFlashCallback.sol';
import '../interfaces/plugin/IAlgebraDynamicFeePlugin.sol';
import '../interfaces/IAlgebraPool.sol';
import '../interfaces/IAlgebraFactory.sol';
import '../interfaces/IAlgebraPoolDeployer.sol';
import '../interfaces/IERC20Minimal.sol';
import '../libraries/TickManagement.sol';
import '../libraries/SafeTransfer.sol';
import '../libraries/Constants.sol';
import '../libraries/Plugins.sol';
import './common/Timestamp.sol';
/// @title Algebra pool base abstract contract
/// @notice Contains state variables, immutables and common internal functions
/// @dev Decoupling into a separate abstract contract simplifies testing
abstract contract AlgebraPoolBase is IAlgebraPool, Timestamp {
using TickManagement for mapping(int24 => TickManagement.Tick);
/// @notice The struct with important state values of pool
/// @dev fits into one storage slot
/// @param price The square root of the current price in Q64.96 format
/// @param tick The current tick (price(tick) <= current price). May not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick boundary
/// @param lastFee The current (last known) fee in hundredths of a bip, i.e. 1e-6 (so 100 is 0.01%). May be obsolete if using dynamic fee plugin
/// @param pluginConfig The current plugin config as bitmap. Each bit is responsible for enabling/disabling the hooks, the last bit turns on/off dynamic fees logic
/// @param communityFee The community fee represented as a percent of all collected fee in thousandths, i.e. 1e-3 (so 100 is 10%)
/// @param unlocked Reentrancy lock flag, true if the pool currently is unlocked, otherwise - false
struct GlobalState {
uint160 price;
int24 tick;
uint16 lastFee;
uint8 pluginConfig;
uint16 communityFee;
bool unlocked;
}
/// @inheritdoc IAlgebraPoolImmutables
uint128 public constant override maxLiquidityPerTick = Constants.MAX_LIQUIDITY_PER_TICK;
/// @inheritdoc IAlgebraPoolImmutables
address public immutable override factory;
/// @inheritdoc IAlgebraPoolImmutables
address public immutable override token0;
/// @inheritdoc IAlgebraPoolImmutables
address public immutable override token1;
// ! IMPORTANT security note: the pool state can be manipulated
// ! external contracts using this data must prevent read-only reentrancy
/// @inheritdoc IAlgebraPoolState
uint256 public override totalFeeGrowth0Token;
/// @inheritdoc IAlgebraPoolState
uint256 public override totalFeeGrowth1Token;
/// @inheritdoc IAlgebraPoolState
GlobalState public override globalState;
/// @inheritdoc IAlgebraPoolState
mapping(int24 => TickManagement.Tick) public override ticks;
/// @dev The amounts of token0 and token1 that will be sent to the vault
uint104 internal communityFeePending0;
uint104 internal communityFeePending1;
/// @inheritdoc IAlgebraPoolState
uint32 public override lastFeeTransferTimestamp;
uint104 internal pluginFeePending0;
uint104 internal pluginFeePending1;
/// @inheritdoc IAlgebraPoolState
address public override plugin;
/// @inheritdoc IAlgebraPoolState
address public override communityVault;
/// @inheritdoc IAlgebraPoolState
mapping(int16 => uint256) public override tickTable;
/// @inheritdoc IAlgebraPoolState
int24 public override nextTickGlobal;
/// @inheritdoc IAlgebraPoolState
int24 public override prevTickGlobal;
/// @inheritdoc IAlgebraPoolState
uint128 public override liquidity;
/// @inheritdoc IAlgebraPoolState
int24 public override tickSpacing;
// shares one slot with TickStructure.tickTreeRoot
/// @notice Check that the lower and upper ticks do not violate the boundaries of allowed ticks and are specified in the correct order
modifier onlyValidTicks(int24 bottomTick, int24 topTick) {
TickManagement.checkTickRangeValidity(bottomTick, topTick);
_;
}
constructor() {
address _plugin;
(_plugin, factory, token0, token1) = _getDeployParameters();
(prevTickGlobal, nextTickGlobal) = (TickMath.MIN_TICK, TickMath.MAX_TICK);
globalState.unlocked = true;
if (_plugin != address(0)) {
_setPlugin(_plugin);
}
}
/// @inheritdoc IAlgebraPoolState
/// @dev safe from read-only reentrancy getter function
function safelyGetStateOfAMM()
external
view
override
returns (uint160 sqrtPrice, int24 tick, uint16 lastFee, uint8 pluginConfig, uint128 activeLiquidity, int24 nextTick, int24 previousTick)
{
sqrtPrice = globalState.price;
tick = globalState.tick;
lastFee = globalState.lastFee;
pluginConfig = globalState.pluginConfig;
bool unlocked = globalState.unlocked;
if (!unlocked) revert IAlgebraPoolErrors.locked();
activeLiquidity = liquidity;
nextTick = nextTickGlobal;
previousTick = prevTickGlobal;
}
/// @inheritdoc IAlgebraPoolState
function isUnlocked() external view override returns (bool unlocked) {
return globalState.unlocked;
}
/// @inheritdoc IAlgebraPoolState
function getCommunityFeePending() external view override returns (uint128, uint128) {
return (communityFeePending0, communityFeePending1);
}
function getPluginFeePending() external view override returns (uint128, uint128) {
return (pluginFeePending0, pluginFeePending1);
}
/// @inheritdoc IAlgebraPoolState
function fee() external view override returns (uint16 currentFee) {
currentFee = globalState.lastFee;
uint8 pluginConfig = globalState.pluginConfig;
if (Plugins.hasFlag(pluginConfig, Plugins.DYNAMIC_FEE)) return IAlgebraDynamicFeePlugin(plugin).getCurrentFee();
}
/// @dev Gets the parameter values for creating the pool. They are not passed in the constructor to make it easier to use create2 opcode
/// Can be overridden in tests
function _getDeployParameters() internal virtual returns (address, address, address, address) {
return IAlgebraPoolDeployer(msg.sender).getDeployParameters();
}
/// @dev Gets the default settings for pool initialization. Can be overridden in tests
function _getDefaultConfiguration() internal virtual returns (uint16, int24, uint16) {
return IAlgebraFactory(factory).defaultConfigurationForPool();
}
// The main external calls that are used by the pool. Can be overridden in tests
function _balanceToken0() internal view virtual returns (uint256) {
return IERC20Minimal(token0).balanceOf(address(this));
}
function _balanceToken1() internal view virtual returns (uint256) {
return IERC20Minimal(token1).balanceOf(address(this));
}
function _transfer(address token, address to, uint256 amount) internal virtual {
SafeTransfer.safeTransfer(token, to, amount);
}
// These 'callback' functions are wrappers over the callbacks that the pool calls on the msg.sender
// These methods can be overridden in tests
/// @dev Using function to save bytecode
function _swapCallback(int256 amount0, int256 amount1, bytes calldata data) internal virtual {
IAlgebraSwapCallback(msg.sender).algebraSwapCallback(amount0, amount1, data);
}
function _mintCallback(uint256 amount0, uint256 amount1, bytes calldata data) internal virtual {
IAlgebraMintCallback(msg.sender).algebraMintCallback(amount0, amount1, data);
}
function _flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) internal virtual {
IAlgebraFlashCallback(msg.sender).algebraFlashCallback(fee0, fee1, data);
}
// This virtual function is implemented in TickStructure and used in Positions
/// @dev Add or remove a pair of ticks to the corresponding data structure
function _addOrRemoveTicks(int24 bottomTick, int24 topTick, bool toggleBottom, bool toggleTop, int24 currentTick, bool remove) internal virtual;
function _setCommunityFee(uint16 _communityFee) internal {
globalState.communityFee = _communityFee;
emit CommunityFee(_communityFee);
}
function _setCommunityFeeVault(address _communityFeeVault) internal {
communityVault = _communityFeeVault;
emit CommunityVault(_communityFeeVault);
}
function _setFee(uint16 _fee) internal {
globalState.lastFee = _fee;
emit Fee(_fee);
}
function _setTickSpacing(int24 _tickSpacing) internal {
tickSpacing = _tickSpacing;
emit TickSpacing(_tickSpacing);
}
function _setPlugin(address _plugin) internal {
plugin = _plugin;
emit Plugin(_plugin);
}
function _setPluginConfig(uint8 _pluginConfig) internal {
globalState.pluginConfig = _pluginConfig;
emit PluginConfig(_pluginConfig);
}
}
"
},
"contracts/base/common/Timestamp.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0 <0.9.0;
/// @title Abstract contract with modified blockTimestamp functionality
/// @notice Allows the pool and other contracts to get a timestamp truncated to 32 bits
/// @dev Can be overridden in tests to make testing easier
abstract contract Timestamp {
/// @dev This function is created for testing by overriding it.
/// @return A timestamp converted to uint32
function _blockTimestamp() internal view virtual returns (uint32) {
return uint32(block.timestamp); // truncation is desired
}
}
"
},
"contracts/base/Positions.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
import '../libraries/LiquidityMath.sol';
import '../libraries/TickManagement.sol';
import './AlgebraPoolBase.sol';
/// @title Algebra positions abstract contract
/// @notice Contains the logic of recalculation and change of liquidity positions
/// @dev Relies on method _addOrRemoveTicks, which is implemented in TickStructure
abstract contract Positions is AlgebraPoolBase {
using TickManagement for mapping(int24 => TickManagement.Tick);
struct Position {
uint256 liquidity; // The amount of liquidity concentrated in the range
uint256 innerFeeGrowth0Token; // The last updated fee growth per unit of liquidity
uint256 innerFeeGrowth1Token;
uint128 fees0; // The amount of token0 owed to a LP
uint128 fees1; // The amount of token1 owed to a LP
}
/// @inheritdoc IAlgebraPoolState
mapping(bytes32 => Position) public override positions;
/// @notice This function fetches certain position object
/// @param owner The address owing the position
/// @param bottomTick The position's bottom tick
/// @param topTick The position's top tick
/// @return position The Position object
function getOrCreatePosition(address owner, int24 bottomTick, int24 topTick) internal view returns (Position storage) {
bytes32 key;
assembly {
key := or(shl(24, or(shl(24, owner), and(bottomTick, 0xFFFFFF))), and(topTick, 0xFFFFFF))
}
return positions[key];
}
/// @dev Updates position's ticks and its fees
/// @return amount0 The abs amount of token0 that corresponds to liquidityDelta
/// @return amount1 The abs amount of token1 that corresponds to liquidityDelta
function _updatePositionTicksAndFees(
Position storage position,
int24 bottomTick,
int24 topTick,
int128 liquidityDelta
) internal returns (uint256 amount0, uint256 amount1) {
(uint160 currentPrice, int24 currentTick) = (globalState.price, globalState.tick);
bool toggledBottom;
bool toggledTop;
{
// scope to prevent "stack too deep"
(uint256 _totalFeeGrowth0, uint256 _totalFeeGrowth1) = (totalFeeGrowth0Token, totalFeeGrowth1Token);
if (liquidityDelta != 0) {
toggledBottom = ticks.update(bottomTick, currentTick, liquidityDelta, _totalFeeGrowth0, _totalFeeGrowth1, false); // isTopTick: false
toggledTop = ticks.update(topTick, currentTick, liquidityDelta, _totalFeeGrowth0, _totalFeeGrowth1, true); // isTopTick: true
}
(uint256 feeGrowth0, uint256 feeGrowth1) = ticks.getInnerFeeGrowth(bottomTick, topTick, currentTick, _totalFeeGrowth0, _totalFeeGrowth1);
_recalculatePosition(position, liquidityDelta, feeGrowth0, feeGrowth1);
}
if (liquidityDelta != 0) {
// if liquidityDelta is negative and the tick was toggled, it means that it should not be initialized anymore, so we delete it
if (toggledBottom || toggledTop) {
_addOrRemoveTicks(bottomTick, topTick, toggledBottom, toggledTop, currentTick, liquidityDelta < 0);
}
int128 globalLiquidityDelta;
(amount0, amount1, globalLiquidityDelta) = LiquidityMath.getAmountsForLiquidity(bottomTick, topTick, liquidityDelta, currentTick, currentPrice);
if (globalLiquidityDelta != 0) liquidity = LiquidityMath.addDelta(liquidity, liquidityDelta); // update global liquidity
}
}
/// @notice Increases amounts of tokens owed to owner of the position
/// @param position The position object to operate with
/// @param liquidityDelta The amount on which to increase\decrease the liquidity
/// @param innerFeeGrowth0Token Total fee token0 fee growth per liquidity between position's lower and upper ticks
/// @param innerFeeGrowth1Token Total fee token1 fee growth per liquidity between position's lower and upper ticks
function _recalculatePosition(
Position storage position,
int128 liquidityDelta,
uint256 innerFeeGrowth0Token,
uint256 innerFeeGrowth1Token
) internal {
uint128 liquidityBefore = uint128(position.liquidity);
if (liquidityDelta == 0) {
if (liquidityBefore == 0) return; // Do not recalculate the empty ranges
} else {
// change position liquidity
position.liquidity = LiquidityMath.addDelta(liquidityBefore, liquidityDelta);
}
unchecked {
// update the position
(uint256 lastInnerFeeGrowth0Token, uint256 lastInnerFeeGrowth1Token) = (position.innerFeeGrowth0Token, position.innerFeeGrowth1Token);
uint128 fees0;
if (lastInnerFeeGrowth0Token != innerFeeGrowth0Token) {
position.innerFeeGrowth0Token = innerFeeGrowth0Token;
fees0 = uint128(FullMath.mulDiv(innerFeeGrowth0Token - lastInnerFeeGrowth0Token, liquidityBefore, Constants.Q128));
}
uint128 fees1;
if (lastInnerFeeGrowth1Token != innerFeeGrowth1Token) {
position.innerFeeGrowth1Token = innerFeeGrowth1Token;
fees1 = uint128(FullMath.mulDiv(innerFeeGrowth1Token - lastInnerFeeGrowth1Token, liquidityBefore, Constants.Q128));
}
// To avoid overflow owner has to collect fee before it
if (fees0 | fees1 != 0) {
position.fees0 += fees0;
position.fees1 += fees1;
}
}
}
}
"
},
"contracts/base/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
import './AlgebraPoolBase.sol';
/// @title Algebra reentrancy protection
/// @notice Provides a modifier that protects against reentrancy
abstract contract ReentrancyGuard is AlgebraPoolBase {
/// @notice checks that reentrancy lock is unlocked
modifier onlyUnlocked() {
_checkUnlocked();
_;
}
/// @dev using private function to save bytecode
function _checkUnlocked() internal view {
if (!globalState.unlocked) revert IAlgebraPoolErrors.locked();
}
/// @dev using private function to save bytecode
function _lock() internal {
if (!globalState.unlocked) revert IAlgebraPoolErrors.locked();
globalState.unlocked = false;
}
/// @dev using private function to save bytecode
function _unlock() internal {
globalState.unlocked = true;
}
}
"
},
"contracts/base/ReservesManager.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
import '../libraries/SafeCast.sol';
import '../libraries/Plugins.sol';
import './AlgebraPoolBase.sol';
import '../interfaces/plugin/IAlgebraPlugin.sol';
import '../interfaces/pool/IAlgebraPoolErrors.sol';
/// @title Algebra reserves management abstract contract
/// @notice Encapsulates logic for tracking and changing pool reserves
/// @dev The reserve mechanism allows the pool to keep track of unexpected increases in balances
abstract contract ReservesManager is AlgebraPoolBase {
using Plugins for bytes4;
using SafeCast for uint256;
/// @dev The tracked token0 and token1 reserves of pool
uint128 internal reserve0;
uint128 internal reserve1;
/// @inheritdoc IAlgebraPoolState
function getReserves() external view returns (uint128, uint128) {
return (reserve0, reserve1);
}
/// @dev updates reserves data and distributes excess in the form of fee to liquidity providers.
/// If any of the balances is greater than uint128, the excess is sent to the communityVault
function _updateReserves() internal returns (uint256 balance0, uint256 balance1) {
(balance0, balance1) = (_balanceToken0(), _balanceToken1());
// we do not support tokens with totalSupply > type(uint128).max, so any excess will be sent to communityVault
// this situation can only occur if the tokens are sent directly to the pool from outside
// **such excessive tokens will be burned if there is no communityVault connected**
if (balance0 > type(uint128).max || balance1 > type(uint128).max) {
unchecked {
address _communityVault = communityVault;
if (balance0 > type(uint128).max) {
_transfer(token0, _communityVault, balance0 - type(uint128).max);
balance0 = type(uint128).max;
}
if (balance1 > type(uint128).max) {
_transfer(token1, _communityVault, balance1 - type(uint128).max);
balance1 = type(uint128).max;
}
}
}
uint128 _liquidity = liquidity;
if (_liquidity == 0) return (balance0, balance1);
(uint128 _reserve0, uint128 _reserve1) = (reserve0, reserve1);
(bool hasExcessToken0, bool hasExcessToken1) = (balance0 > _reserve0, balance1 > _reserve1);
if (hasExcessToken0 || hasExcessToken1) {
unchecked {
if (hasExcessToken0) totalFeeGrowth0Token += FullMath.mulDiv(balance0 - _reserve0, Constants.Q128, _liquidity);
if (hasExcessToken1) totalFeeGrowth1Token += FullMath.mulDiv(balance1 - _reserve1, Constants.Q128, _liquidity);
emit ExcessTokens(balance0 - _reserve0, balance1 - _reserve1);
(reserve0, reserve1) = (uint128(balance0), uint128(balance1));
}
}
}
/// @notice Forces reserves to match balances. Excess of tokens will be sent to `receiver`
function _skimReserves(address receiver) internal {
(uint256 balance0, uint256 balance1) = (_balanceToken0(), _balanceToken1());
(uint128 _reserve0, uint128 _reserve1) = (reserve0, reserve1);
if (balance0 > _reserve0 || balance1 > _reserve1) {
if (balance0 > _reserve0) _transfer(token0, receiver, balance0 - _reserve0);
if (balance1 > _reserve1) _transfer(token1, receiver, balance1 - _reserve1);
emit Skim(receiver, balance0 - _reserve0, balance1 - _reserve1);
}
}
/// @notice Accrues fees and transfers them to `recipient`
/// @dev If we transfer fees, writes zeros to the storage slot specified by the slot argument
/// If we do not transfer fees, returns actual pendingFees
function _accrueAndTransferFees(
uint256 fee0,
uint256 fee1,
uint256 lastTimestamp,
bytes32 receiverSlot,
bytes32 feePendingSlot
) internal returns (uint104, uint104, uint256, uint256) {
if (fee0 | fee1 != 0) {
uint256 feePending0;
uint256 feePending1;
assembly {
// Load the storage slot specified by the slot argument
let sl := sload(feePendingSlot)
// Extract the uint104 value
feePending0 := and(sl, 0xFFFFFFFFFFFFFFFFFFFFFFFFFF)
// Shift right by 104 bits and extract the uint104 value
feePending1 := and(shr(104, sl), 0xFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
feePending0 += fee0;
feePending1 += fee1;
if (
_blockTimestamp() - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY || feePending0 > type(uint104).max || feePending1 > type(uint104).max
) {
// use sload from slot (like pointer dereference) to avoid gas
address recipient;
assembly {
recipient := sload(receiverSlot)
}
(uint256 feeSent0, uint256 feeSent1) = _transferFees(feePending0, feePending1, recipient);
// use sload from slot (like pointer dereference) to avoid gas
// override `lastFeeTransferTimestamp` with zeros is OK
// because we will update it later
assembly {
sstore(feePendingSlot, 0)
}
// sent fees return 0 pending and sent fees
return (0, 0, feeSent0, feeSent1);
} else {
// didn't send fees return pending fees and 0 sent
return (uint104(feePending0), uint104(feePending1), 0, 0);
}
} else {
if (_blockTimestamp() - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) {
uint256 feePending0;
uint256 feePending1;
assembly {
// Load the storage slot specified by the slot argument
let sl := sload(feePendingSlot)
// Extract the uint104 value
feePending0 := and(sl, 0xFFFFFFFFFFFFFFFFFFFFFFFFFF)
// Shift right by 104 bits and extract the uint104 value
feePending1 := and(shr(104, sl), 0xFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
if (feePending0 | feePending1 != 0) {
address recipient;
// use sload from slot (like pointer dereference) to avoid gas
assembly {
recipient := sload(receiverSlot)
}
(uint256 feeSent0, uint256 feeSent1) = _transferFees(feePending0, feePending1, recipient);
// use sload from slot (like pointer dereference) to avoid gas
assembly {
sstore(feePendingSlot, 0)
}
// sent fees return 0 pending and sent fees
return (0, 0, feeSent0, feeSent1);
}
}
// didn't either sent fees or increased pending
return (0, 0, 0, 0);
}
}
function _transferFees(uint256 feePending0, uint256 feePending1, address feesRecipient) private returns (uint256, uint256) {
uint256 feeSent0;
uint256 feeSent1;
if (feePending0 > 0) {
_transfer(token0, feesRecipient, feePending0);
feeSent0 = feePending0;
}
if (feePending1 > 0) {
_transfer(token1, feesRecipient, feePending1);
feeSent1 = feePending1;
}
return (feeSent0, feeSent1);
}
/// @notice Applies deltas to reserves and pays communityFees
/// @dev Community fee is sent to the vault at a specified frequency or when variables communityFeePending{0,1} overflow
/// @param deltaR0 Amount of token0 to add/subtract to/from reserve0, must not exceed uint128
/// @param deltaR1 Amount of token1 to add/subtract to/from reserve1, must not exceed uint128
/// @param communityFee0 Amount of token0 to pay as communityFee, must not exceed uint128
/// @param communityFee1 Amount of token1 to pay as communityFee, must not exceed uint128
function _changeReserves(
int256 deltaR0,
int256 deltaR1,
uint256 communityFee0,
uint256 communityFee1,
uint256 pluginFee0,
uint256 pluginFee1
) internal {
if (communityFee0 > 0 || communityFee1 > 0 || pluginFee0 > 0 || pluginFee1 > 0) {
bytes32 feePendingSlot;
bytes32 feeRecipientSlot;
uint32 lastTimestamp = lastFeeTransferTimestamp;
bool feeSent;
assembly {
feePendingSlot := communityFeePending0.slot
feeRecipientSlot := communityVault.slot
}
// pass feeRecipientSlot to avoid redundant sload of an address
(uint104 feePending0, uint104 feePending1, uint256 feeSent0, uint256 feeSent1) = _accrueAndTransferFees(
communityFee0,
communityFee1,
lastTimestamp,
feeRecipientSlot,
feePendingSlot
);
if (feeSent0 | feeSent1 != 0) {
// sent fees so decrease deltas
(deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256());
feeSent = true;
} else {
// update pending if we accrued fees
if (feePending0 | feePending1 != 0) (communityFeePending0, communityFeePending1) = (feePending0, feePending1);
}
assembly {
feePendingSlot := pluginFeePending0.slot
feeRecipientSlot := plugin.slot
}
// pass feeRecipientSlot to avoid redundant sload of an address
(feePending0, feePending1, feeSent0, feeSent1) = _accrueAndTransferFees(
pluginFee0,
pluginFee1,
lastTimestamp,
feeRecipientSlot,
feePendingSlot
);
if (feeSent0 | feeSent1 != 0) {
// sent fees so decrease deltas
(deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256());
feeSent = true;
// notify plugin about sent fees
IAlgebraPlugin(plugin).handlePluginFee(feeSent0, feeSent1).shouldReturn(IAlgebraPlugin.handlePluginFee.selector);
} else {
// update pending if we accrued fees
if (feePending0 | feePending1 != 0) (pluginFeePending0, pluginFeePending1) = (feePending0, feePending1);
}
if (feeSent) lastFeeTransferTimestamp = _blockTimestamp();
}
if (deltaR0 | deltaR1 == 0) return;
(uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1);
if (deltaR0 != 0) _reserve0 = (uint256(int256(_reserve0) + deltaR0)).toUint128();
if (deltaR1 != 0) _reserve1 = (uint256(int256(_reserve1) + deltaR1)).toUint128();
(reserve0, reserve1) = (uint128(_reserve0), uint128(_reserve1));
}
}
"
},
"contracts/base/SwapCalculation.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
import '../libraries/PriceMovementMath.sol';
import '../libraries/LowGasSafeMath.sol';
import '../libraries/SafeCast.sol';
import './AlgebraPoolBase.sol';
/// @title Algebra swap calculation abstract contract
/// @notice Contains _calculateSwap encapsulating internal logic of swaps
abstract contract SwapCalculation is AlgebraPoolBase {
using TickManagement for mapping(int24 => TickManagement.Tick);
using SafeCast for uint256;
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
struct SwapCalculationCache {
uint256 communityFee; // The community fee of the selling token, uint256 to minimize casts
bool crossedAnyTick; // If we have already crossed at least one active tick
int256 amountRequiredInitial; // The initial value of the exact input\output amount
int256 amountCalculated; // The additive amount of total output\input calculated through the swap
uint256 totalFeeGrowthInput; // The initial totalFeeGrowth + the fee growth during a swap
uint256 totalFeeGrowthOutput; // The initial totalFeeGrowth for output token, should not change during swap
bool exactInput; // Whether the exact input or output is specified
uint24 fee; // The current fee value in hundredths of a bip, i.e. 1e-6
int24 prevInitializedTick; // The previous initialized tick in linked list
int24 nextInitializedTick; // The next initialized tick in linked list
uint24 pluginFee;
}
struct PriceMovementCache {
uint256 stepSqrtPrice; // The Q64.96 sqrt of the price at the start of the step, uint256 to minimize casts
uint256 nextTickPrice; // The Q64.96 sqrt of the price calculated from the _nextTick_, uint256 to minimize casts
uint256 input; // The additive amount of tokens that have been provided
uint256 output; // The additive amount of token that have been withdrawn
uint256 feeAmount; // The total amount of fee earned within a current step
}
struct FeesAmount {
uint256 communityFeeAmount;
uint256 pluginFeeAmount;
}
function _calculateSwap(
uint24 overrideFee,
uint24 pluginFee,
bool zeroToOne,
int256 amountRequired,
uint160 limitSqrtPrice
) internal returns (int256 amount0, int256 amount1, uint160 currentPrice, int24 currentTick, uint128 currentLiquidity, FeesAmount memory fees) {
if (amountRequired == 0) revert zeroAmountRequired();
if (amountRequired == type(int256).min) revert invalidAmountRequired(); // to avoid problems when changing sign
SwapCalculationCache memory cache;
(cache.amountRequiredInitial, cache.exactInput, cache.pluginFee) = (amountRequired, amountRequired > 0, pluginFee);
// load from one storage slot
(currentLiquidity, cache.prevInitializedTick, cache.nextInitializedTick) = (liquidity, prevTickGlobal, nextTickGlobal);
// load from one storage slot too
(currentPrice, currentTick, cache.fee, cache.communityFee) = (globalState.price, globalState.tick, globalState.lastFee, globalState.communityFee);
if (currentPrice == 0) revert notInitialized();
if (overrideFee != 0) {
cache.fee = overrideFee + pluginFee;
if (cache.fee >= 1e6) revert incorrectPluginFee();
} else {
if (pluginFee != 0) {
cache.fee += pluginFee;
if (cache.fee >= 1e6) revert incorrectPluginFee();
}
}
if (zeroToOne) {
if (limitSqrtPrice >= currentPrice || limitSqrtPrice <= TickMath.MIN_SQRT_RATIO) revert invalidLimitSqrtPrice();
cache.totalFeeGrowthInput = totalFeeGrowth0Token;
} else {
if (limitSqrtPrice <= currentPrice || limitSqrtPrice >= TickMath.MAX_SQRT_RATIO) revert invalidLimitSqrtPrice();
cache.totalFeeGrowthInput = totalFeeGrowth1Token;
}
PriceMovementCache memory step;
unchecked {
// swap until there is remaining input or output tokens or we reach the price limit
do {
int24 nextTick = zeroToOne ? cache.prevInitializedTick : cache.nextInitializedTick;
step.stepSqrtPrice = currentPrice;
step.nextTickPrice = TickMath.getSqrtRatioAtTick(nextTick);
(currentPrice, step.input, step.output, step.feeAmount) = PriceMovementMath.movePriceTowardsTarget(
zeroToOne, // if zeroToOne then the price is moving down
currentPrice,
(zeroToOne == (step.nextTickPrice < limitSqrtPrice)) // move the price to the nearest of the next tick and the limit price
? limitSqrtPrice
: uint160(step.nextTickPrice), // cast is safe
currentLiquidity,
amountRequired,
cache.fee
);
if (cache.exactInput) {
amountRequired -= (step.input + step.feeAmount).toInt256(); // decrease remaining input amount
cache.amountCalculated = cache.amountCalculated.sub(step.output.toInt256()); // decrease calculated output amount
} else {
amountRequired += step.output.toInt256(); // increase remaining output amount (since its negative)
cache.amountCalculated = cache.amountCalculated.add((step.input + step.feeAmount).toInt256()); // increase calculated input amount
}
if (cache.communityFee > 0) {
uint256 delta = (step.feeAmount.mul(cache.communityFee)) / Constants.COMMUNITY_FEE_DENOMINATOR;
step.feeAmount -= delta;
fees.communityFeeAmount += delta;
}
if (cache.pluginFee > 0 && cache.fee > 0) {
uint256 delta = FullMath.mulDiv(step.feeAmount, cache.pluginFee, cache.fee);
step.feeAmount -= delta;
fees.pluginFeeAmount += delta;
}
if (currentLiquidity > 0) cache.totalFeeGrowthInput += FullMath.mulDiv(step.feeAmount, Constants.Q128, currentLiquidity);
// min or max tick can not be crossed due to limitSqrtPrice check
if (currentPrice == step.nextTickPrice) {
// crossing tick
if (!cache.crossedAnyTick) {
cache.crossedAnyTick = true;
cache.totalFeeGrowthOutput = zeroToOne ? totalFeeGrowth1Token : totalFeeGrowth0Token;
}
int128 liquidityDelta;
if (zeroToOne) {
(liquidityDelta, cache.prevInitializedTick, ) = ticks.cross(nextTick, cache.totalFeeGrowthInput, cache.totalFeeGrowthOutput);
liquidityDelta = -liquidityDelta;
(currentTick, cache.nextInitializedTick) = (nextTick - 1, nextTick);
} else {
(liquidityDelta, , cache.nextInitializedTick) = ticks.cross(nextTick, cache.totalFeeGrowthOutput, cache.totalFeeGrowthInput);
(currentTick, cache.prevInitializedTick) = (nextTick, nextTick);
}
currentLiquidity = LiquidityMath.addDelta(currentLiquidity, liquidityDelta);
} else if (currentPrice != step.stepSqrtPrice) {
currentTick = TickMath.getTickAtSqrtRatio(currentPrice); // the price has changed but hasn't reached the target
break; // since the price hasn't reached the target, amountRequired should be 0
}
} while (amountRequired != 0 && currentPrice != limitSqrtPrice); // check stop condition
int256 amountSpent = cache.amountRequiredInitial - amountRequired; // spent amount could be less than initially specified (e.g. reached limit)
(amount0, amount1) = zeroToOne == cache.exactInput ? (amountSpent, cache.amountCalculated) : (cache.amountCalculated, amountSpent);
}
(globalState.price, globalState.tick) = (currentPrice, currentTick);
if (cache.crossedAnyTick) {
(liquidity, prevTickGlobal, nextTickGlobal) = (currentLiquidity, cache.prevInitializedTick, cache.nextInitializedTick);
}
if (zeroToOne) {
totalFeeGrowth0Token = cache.totalFeeGrowthInput;
} else {
totalFeeGrowth1Token = cache.totalFeeGrowthInput;
}
}
}
"
},
"contracts/base/TickStructure.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;
import '../libraries/TickManagement.sol';
import '../libraries/TickTree.sol';
import './AlgebraPoolBase.sol';
/// @title Algebra tick structure abstract contract
/// @notice Encapsulates the logic of interaction with the data structure with ticks
/// @dev Ticks are stored as a doubly linked list. A three-level bitmap tree is used to search through the list
abstract contract TickStructure is AlgebraPoolBase {
using TickManagement for mapping(int24 => TickManagement.Tick);
using TickTree for mapping(int16 => uint256);
/// @inheritdoc IAlgebraPoolState
uint32 public override tickTreeRoot; // The root bitmap of search tree
/// @inheritdoc IAlgebraPoolState
mapping(int16 => uint256) public override tickTreeSecondLayer; // The second layer of search tree
// the leaves of the tree are stored in `tickTable`
constructor() {
ticks.initTickState();
}
/// @notice Used to add or remove a tick from a doubly linked list and search tree
/// @param tick The tick being removed or added now
/// @param currentTick The current global tick in the pool
/// @param oldTickTreeRoot The current tick tree root
/// @param prevInitializedTick Previous active tick before `currentTick`
/// @param nextInitializedTick Next active tick after `currentTick`
/// @param remove Remove or add the tick
/// @return New previous active tick before `currentTick` if changed
/// @return New next active tick after `currentTick` if changed
/// @return New tick tree root if changed
function _addOrRemoveTick(
int24 tick,
int24 currentTick,
uint32 oldTickTreeRoot,
int24 prevInitializedTick,
int24 nextInitializedTick,
bool remove
) internal returns (int24, int24, uint32) {
if (remove) {
(int24 prevTick, int24 nextTick) = ticks.removeTick(tick);
if (prevInitializedTick == tick) prevInitializedTick = prevTick;
else if (nextInitializedTick == tick) nextInitializedTick = nextTick;
} else {
int24 prevTick;
int24 nextTick;
if (prevInitializedTick < tick && nextInitializedTick > tick) {
(prevTick, nextTick) = (prevInitializedTick, nextInitializedTick); // we know next and prev ticks
if (tick > currentTick) nextInitializedTick = tick;
else prevInitializedTick = tick;
} else {
nextTick = tickTable.getNextTick(tickTreeSecondLayer, oldTickTreeRoot, tick);
prevTick = ticks[nextTick].prevTick;
}
ticks.insertTick(tick, prevTick, nextTick);
}
uint32 newTickTreeRoot = tickTable.toggleTick(tickTreeSecondLayer, oldTickTreeRoot, tick);
return (prevInitializedTick, nextInitializedTick, newTickTreeRoot);
}
/// @notice Used to add or remove a pair of ticks from a doubly linked list and search tree
/// @param bottomTick The bottom tick being removed or added now
/// @param topTick The top tick being removed or added now
/// @param toggleBottom Should bottom tick be changed or not
/// @param toggleTop Should top tick be changed or not
/// @param currentTick The current global tick in the pool
/// @param remove Remove or add the ticks
function _addOrRemoveTicks(int24 bottomTick, int24 topTick, bool toggleBottom, bool toggleTop, int24 currentTick, bool remove) internal override {
(int24 prevInitializedTick, int24 nextInitializedTick, uint32 oldTickTreeRoot) = (prevTickGlobal, nextTickGlobal, tickTreeRoot);
(int24 newPrevTick, int24 newNextTick, uint32 newTreeRoot) = (prevInitializedTick, nextInitializedTick, oldTickTreeRoot);
if (toggleBottom) {
(newPrevTick, newNextTick, newTreeRoot) = _addOrRemoveTick(bottomTick, currentTick, newTreeRoot, newPrevTick, newNextTick, remove);
}
if (toggleTop) {
(newPrevTick, newNextTick, newTreeRoot) = _addOrRemoveTick(topTick, currentTick, newTreeRoot, newPrevTick, newNextTick, remove);
}
if (prevInitializedTick != newPrevTick || nextInitializedTick != newNextTick || newTreeRoot != oldTickTreeRoot) {
(prevTickGlobal, nextTickGlobal, tickTreeRoot) = (newPrevTick, newNextTick, newTreeRoot);
}
}
}
"
},
"contracts/interfaces/callback/IAlgebraFlashCallback.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IAlgebraPoolActions#flash
/// @notice Any contract that calls IAlgebraPoolActions#flash must implement this interface
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraFlashCallback {
/// @notice Called to `msg.sender` after transferring to the recipient from IAlgebraPool#flash.
/// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts.
/// The caller of this method _must_ be checked to be a AlgebraPool deployed by the canonical AlgebraFactory.
/// @param fee0 The fee amount in token0 due to the pool by the end of the flash
/// @param fee1 The fee amount in token1 due to the pool by the end of the flash
/// @param data Any data passed through by the caller via the IAlgebraPoolActions#flash call
function algebraFlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external;
}
"
},
"contracts/interfaces/callback/IAlgebraMintCallback.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IAlgebraPoolActions#mint
/// @notice Any contract that calls IAlgebraPoolActions#mint must implement this interface
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraMintCallback {
/// @notice Called to `msg.sender` after minting liquidity to a position from IAlgebraPool#mint.
/// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
/// The caller of th
Submitted on: 2025-11-06 14:18:44
Comments
Log in to comment.
No comments yet.