-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity =0.8.20; | ||
|
||
import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol'; | ||
import '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol'; | ||
|
||
import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol'; | ||
import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol'; | ||
import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolState.sol'; | ||
import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; | ||
|
||
|
||
import './base/SlidingFeeModule.sol'; | ||
|
||
import './interfaces/IAlgebraBasePluginV1.sol'; | ||
import './interfaces/IBasePluginV1Factory.sol'; | ||
import './interfaces/IAlgebraVirtualPool.sol'; | ||
|
||
import './libraries/VolatilityOracle.sol'; | ||
import './libraries/AdaptiveFee.sol'; | ||
import './types/AlgebraFeeConfigurationU144.sol'; | ||
|
||
/// @title Algebra Integral 1.1 default plugin | ||
/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages | ||
contract AlgebraBasePluginV2 is SlidingFeeModule, IAlgebraBasePluginV1, IAlgebraPlugin { | ||
using Plugins for uint8; | ||
using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfiguration; | ||
|
||
uint256 internal constant UINT16_MODULO = 65536; | ||
using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; | ||
|
||
/// @dev The role can be granted in AlgebraFactory | ||
bytes32 public constant ALGEBRA_BASE_PLUGIN_MANAGER = keccak256('ALGEBRA_BASE_PLUGIN_MANAGER'); | ||
|
||
/// @inheritdoc IAlgebraPlugin | ||
uint8 public constant override defaultPluginConfig = uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG | Plugins.DYNAMIC_FEE); | ||
|
||
/// @inheritdoc IFarmingPlugin | ||
address public immutable override pool; | ||
address private immutable factory; | ||
address private immutable pluginFactory; | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
VolatilityOracle.Timepoint[UINT16_MODULO] public override timepoints; | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
uint16 public override timepointIndex; | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
uint32 public override lastTimepointTimestamp; | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
bool public override isInitialized; | ||
|
||
/// @dev AlgebraFeeConfiguration struct packed in uint144 | ||
AlgebraFeeConfigurationU144 private _feeConfig; | ||
|
||
/// @inheritdoc IFarmingPlugin | ||
address public override incentive; | ||
|
||
/// @dev the address which connected the last incentive. Needed so that he can disconnect it | ||
address private _lastIncentiveOwner; | ||
|
||
modifier onlyPool() { | ||
_checkIfFromPool(); | ||
_; | ||
} | ||
|
||
constructor(address _pool, address _factory, address _pluginFactory) SlidingFeeModule() { | ||
(factory, pool, pluginFactory) = (_factory, _pool, _pluginFactory); | ||
} | ||
|
||
/// @inheritdoc IDynamicFeeManager | ||
function feeConfig() | ||
external | ||
view | ||
override | ||
returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee) | ||
{ | ||
(alpha1, alpha2) = (_feeConfig.alpha1(), _feeConfig.alpha2()); | ||
(beta1, beta2) = (_feeConfig.beta1(), _feeConfig.beta2()); | ||
(gamma1, gamma2) = (_feeConfig.gamma1(), _feeConfig.gamma2()); | ||
baseFee = _feeConfig.baseFee(); | ||
} | ||
|
||
function _checkIfFromPool() internal view { | ||
require(msg.sender == pool, 'Only pool can call this'); | ||
} | ||
|
||
function _getPoolState() internal view returns (uint160 price, int24 tick, uint16 fee, uint8 pluginConfig) { | ||
(price, tick, fee, pluginConfig, , ) = IAlgebraPoolState(pool).globalState(); | ||
} | ||
|
||
function _getPluginInPool() internal view returns (address plugin) { | ||
return IAlgebraPool(pool).plugin(); | ||
} | ||
|
||
/// @inheritdoc IAlgebraBasePluginV1 | ||
function initialize() external override { | ||
require(!isInitialized, 'Already initialized'); | ||
require(_getPluginInPool() == address(this), 'Plugin not attached'); | ||
(uint160 price, int24 tick, , ) = _getPoolState(); | ||
require(price != 0, 'Pool is not initialized'); | ||
|
||
uint32 time = _blockTimestamp(); | ||
timepoints.initialize(time, tick); | ||
lastTimepointTimestamp = time; | ||
isInitialized = true; | ||
|
||
_updatePluginConfigInPool(); | ||
} | ||
|
||
// ###### Volatility and TWAP oracle ###### | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
function getSingleTimepoint(uint32 secondsAgo) external view override returns (int56 tickCumulative, uint88 volatilityCumulative) { | ||
// `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared: they may differ due to interpolation errors | ||
(, int24 tick, , ) = _getPoolState(); | ||
uint16 lastTimepointIndex = timepointIndex; | ||
uint16 oldestIndex = timepoints.getOldestIndex(lastTimepointIndex); | ||
VolatilityOracle.Timepoint memory result = timepoints.getSingleTimepoint(_blockTimestamp(), secondsAgo, tick, lastTimepointIndex, oldestIndex); | ||
(tickCumulative, volatilityCumulative) = (result.tickCumulative, result.volatilityCumulative); | ||
} | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
function getTimepoints( | ||
uint32[] memory secondsAgos | ||
) external view override returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { | ||
// `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared: they may differ due to interpolation errors | ||
(, int24 tick, , ) = _getPoolState(); | ||
return timepoints.getTimepoints(_blockTimestamp(), secondsAgos, tick, timepointIndex); | ||
} | ||
|
||
/// @inheritdoc IVolatilityOracle | ||
function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external override { | ||
require(!timepoints[startIndex].initialized); // if not initialized, then all subsequent ones too | ||
require(amount > 0 && type(uint16).max - startIndex >= amount); | ||
|
||
unchecked { | ||
for (uint256 i = startIndex; i < startIndex + amount; ++i) { | ||
timepoints[i].blockTimestamp = 1; // will be overwritten | ||
} | ||
} | ||
} | ||
|
||
// ###### Fee manager ###### | ||
|
||
/// @inheritdoc IDynamicFeeManager | ||
function changeFeeConfiguration(AlgebraFeeConfiguration calldata _config) external override { | ||
require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); | ||
AdaptiveFee.validateFeeConfiguration(_config); | ||
|
||
_feeConfig = _config.pack(); // pack struct to uint144 and write in storage | ||
emit FeeConfiguration(_config); | ||
} | ||
|
||
function setPriceChangeFactor(uint256 _priceChangeFactor) external { | ||
require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); | ||
s_priceChangeFactor = _priceChangeFactor; | ||
|
||
emit PriceChangeFactor(_priceChangeFactor); | ||
} | ||
|
||
/// @inheritdoc IAlgebraDynamicFeePlugin | ||
function getCurrentFee() external view override returns (uint16 fee) { | ||
(, , fee, ) = _getPoolState(); | ||
} | ||
|
||
function _getFeeAtLastTimepoint( | ||
uint160 currentPrice, | ||
uint16 lastTimepoinIntex, | ||
uint16 currentPoolFee, | ||
bool zeroToOne | ||
) internal returns (uint16 fee) { | ||
VolatilityOracle.Timepoint memory lastTimepoint = timepoints[lastTimepoinIntex]; | ||
|
||
return _getFeeAndUpdateFactors(currentPrice, TickMath.getSqrtRatioAtTick(lastTimepoint.tick), currentPoolFee, zeroToOne); | ||
} | ||
|
||
// ###### Farming plugin ###### | ||
|
||
/// @inheritdoc IFarmingPlugin | ||
function setIncentive(address newIncentive) external override { | ||
bool toConnect = newIncentive != address(0); | ||
bool accessAllowed; | ||
if (toConnect) { | ||
accessAllowed = msg.sender == IBasePluginV1Factory(pluginFactory).farmingAddress(); | ||
} else { | ||
// we allow the one who connected the incentive to disconnect it, | ||
// even if he no longer has the rights to connect incentives | ||
if (_lastIncentiveOwner != address(0)) accessAllowed = msg.sender == _lastIncentiveOwner; | ||
if (!accessAllowed) accessAllowed = msg.sender == IBasePluginV1Factory(pluginFactory).farmingAddress(); | ||
} | ||
require(accessAllowed, 'Not allowed to set incentive'); | ||
|
||
bool isPluginConnected = _getPluginInPool() == address(this); | ||
if (toConnect) require(isPluginConnected, 'Plugin not attached'); | ||
|
||
address currentIncentive = incentive; | ||
require(currentIncentive != newIncentive, 'Already active'); | ||
if (toConnect) require(currentIncentive == address(0), 'Has active incentive'); | ||
|
||
incentive = newIncentive; | ||
emit Incentive(newIncentive); | ||
|
||
if (toConnect) { | ||
_lastIncentiveOwner = msg.sender; // write creator of this incentive | ||
} else { | ||
_lastIncentiveOwner = address(0); | ||
} | ||
|
||
if (isPluginConnected) { | ||
_updatePluginConfigInPool(); | ||
} | ||
} | ||
|
||
/// @inheritdoc IFarmingPlugin | ||
function isIncentiveConnected(address targetIncentive) external view override returns (bool) { | ||
if (incentive != targetIncentive) return false; | ||
if (_getPluginInPool() != address(this)) return false; | ||
(, , , uint8 pluginConfig) = _getPoolState(); | ||
if (!pluginConfig.hasFlag(Plugins.AFTER_SWAP_FLAG)) return false; | ||
|
||
return true; | ||
} | ||
|
||
// ###### HOOKS ###### | ||
|
||
function beforeInitialize(address, uint160) external override onlyPool returns (bytes4) { | ||
_updatePluginConfigInPool(); | ||
return IAlgebraPlugin.beforeInitialize.selector; | ||
} | ||
|
||
function afterInitialize(address, uint160, int24 tick) external override onlyPool returns (bytes4) { | ||
uint32 _timestamp = _blockTimestamp(); | ||
timepoints.initialize(_timestamp, tick); | ||
|
||
lastTimepointTimestamp = _timestamp; | ||
isInitialized = true; | ||
|
||
IAlgebraPool(pool).setFee(_feeConfig.baseFee()); | ||
return IAlgebraPlugin.afterInitialize.selector; | ||
} | ||
|
||
/// @dev unused | ||
function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external override onlyPool returns (bytes4) { | ||
_updatePluginConfigInPool(); // should not be called, reset config | ||
return IAlgebraPlugin.beforeModifyPosition.selector; | ||
} | ||
|
||
/// @dev unused | ||
function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) { | ||
_updatePluginConfigInPool(); // should not be called, reset config | ||
return IAlgebraPlugin.afterModifyPosition.selector; | ||
} | ||
|
||
function beforeSwap(address, address, bool zeroToOne, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4) { | ||
_writeTimepointAndUpdateFee(zeroToOne); | ||
return IAlgebraPlugin.beforeSwap.selector; | ||
} | ||
|
||
function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes calldata) external override onlyPool returns (bytes4) { | ||
address _incentive = incentive; | ||
if (_incentive != address(0)) { | ||
(, int24 tick, , ) = _getPoolState(); | ||
IAlgebraVirtualPool(_incentive).crossTo(tick, zeroToOne); | ||
} else { | ||
_updatePluginConfigInPool(); // should not be called, reset config | ||
} | ||
|
||
return IAlgebraPlugin.afterSwap.selector; | ||
} | ||
|
||
/// @dev unused | ||
function beforeFlash(address, address, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) { | ||
_updatePluginConfigInPool(); // should not be called, reset config | ||
return IAlgebraPlugin.beforeFlash.selector; | ||
} | ||
|
||
/// @dev unused | ||
function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) { | ||
_updatePluginConfigInPool(); // should not be called, reset config | ||
return IAlgebraPlugin.afterFlash.selector; | ||
} | ||
|
||
function _updatePluginConfigInPool() internal { | ||
uint8 newPluginConfig = defaultPluginConfig; | ||
if (incentive != address(0)) { | ||
newPluginConfig |= uint8(Plugins.AFTER_SWAP_FLAG); | ||
} | ||
|
||
(, , , uint8 currentPluginConfig) = _getPoolState(); | ||
if (currentPluginConfig != newPluginConfig) { | ||
IAlgebraPool(pool).setPluginConfig(newPluginConfig); | ||
} | ||
} | ||
|
||
function _writeTimepointAndUpdateFee(bool zeroToOne) internal { | ||
// single SLOAD | ||
uint16 _lastIndex = timepointIndex; | ||
uint32 _lastTimepointTimestamp = lastTimepointTimestamp; | ||
AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; // struct packed in uint144 | ||
bool _isInitialized = isInitialized; | ||
require(_isInitialized, 'Not initialized'); | ||
|
||
uint32 currentTimestamp = _blockTimestamp(); | ||
|
||
if (_lastTimepointTimestamp == currentTimestamp) return; | ||
|
||
(uint160 currentPrice, int24 tick, uint16 fee, ) = _getPoolState(); | ||
(uint16 newLastIndex, uint16 newOldestIndex) = timepoints.write(_lastIndex, currentTimestamp, tick); | ||
|
||
timepointIndex = newLastIndex; | ||
lastTimepointTimestamp = currentTimestamp; | ||
|
||
uint16 newFee = _getFeeAtLastTimepoint(currentPrice, newLastIndex, fee, zeroToOne); | ||
if (newFee != fee) { | ||
IAlgebraPool(pool).setFee(newFee); | ||
} | ||
} | ||
} |
Oops, something went wrong.