Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sliding fee module inside AlebraBasePluginV2 #120

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
328 changes: 328 additions & 0 deletions src/plugin/contracts/AlgebraBasePluginV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
// 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(
int24 currentTick,
uint16 lastTimepoinIntex,
uint16 currentPoolFee,
bool zeroToOne
) internal returns (uint16 fee) {
VolatilityOracle.Timepoint memory lastTimepoint = timepoints[lastTimepoinIntex];

return _getFeeAndUpdateFactors(currentTick, 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();
// console.log('current price in write: ', currentPrice);
// console.log('current price in tick: ', TickMath.getSqrtRatioAtTick(tick));
// console.log('last index: ', _lastIndex);
(uint16 newLastIndex, uint16 newOldestIndex) = timepoints.write(_lastIndex, currentTimestamp, tick);
// console.log('newlast index: ', newLastIndex);
// {
// VolatilityOracle.Timepoint memory lastTimepoint = timepoints[newLastIndex];
// console.log('last timepoint index: ', TickMath.getSqrtRatioAtTick(lastTimepoint.tick));
// }

timepointIndex = newLastIndex;
lastTimepointTimestamp = currentTimestamp;

uint16 newFee = _getFeeAtLastTimepoint(tick, _lastIndex, fee, zeroToOne);
if (newFee != fee) {
IAlgebraPool(pool).setFee(newFee);
}
}
}
Loading
Loading