Skip to content

Fee discount plugin #137

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

Open
wants to merge 4 commits into
base: integral-v1.2
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
79 changes: 79 additions & 0 deletions src/plugin/contracts/AlgebraFeeDiscountPlugin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;

import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol';

import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol';

import './plugins/DynamicFeePlugin.sol';
import './plugins/FarmingProxyPlugin.sol';
import './plugins/FeeDiscountPlugin.sol';
import './plugins/VolatilityOraclePlugin.sol';
import 'hardhat/console.sol';

/// @title Algebra Integral 1.2 discount fee plugin
contract AlgebraFeeDiscountPlugin is DynamicFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin, FeeDiscountPlugin {
using Plugins for uint8;

/// @inheritdoc IAlgebraPlugin
uint8 public constant override defaultPluginConfig =
uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG | Plugins.AFTER_SWAP_FLAG | Plugins.DYNAMIC_FEE);

constructor(address _pool, address _factory, address _pluginFactory) BasePlugin(_pool, _factory, _pluginFactory) {}

// ###### HOOKS ######

function beforeInitialize(address, uint160) external override onlyPool returns (bytes4) {
_updatePluginConfigInPool(defaultPluginConfig);
return IAlgebraPlugin.beforeInitialize.selector;
}

function afterInitialize(address, uint160, int24 tick) external override onlyPool returns (bytes4) {
_initialize_TWAP(tick);

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, uint24) {
_updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config
return (IAlgebraPlugin.beforeModifyPosition.selector, 0);
}

/// @dev unused
function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) {
_updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config
return IAlgebraPlugin.afterModifyPosition.selector;
}

function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4, uint24, uint24) {
_writeTimepoint();
uint88 volatilityAverage = _getAverageVolatilityLast();
uint24 fee = _getCurrentFee(volatilityAverage);
fee = _applyFeeDiscount(tx.origin, msg.sender, fee);
return (IAlgebraPlugin.beforeSwap.selector, fee, 0);
}

function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes calldata) external override onlyPool returns (bytes4) {
_updateVirtualPoolTick(zeroToOne);
return IAlgebraPlugin.afterSwap.selector;
}

/// @dev unused
function beforeFlash(address, address, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) {
_updatePluginConfigInPool(defaultPluginConfig); // 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(defaultPluginConfig); // should not be called, reset config
return IAlgebraPlugin.afterFlash.selector;
}

function getCurrentFee() external view override returns (uint16 fee) {
uint88 volatilityAverage = _getAverageVolatilityLast();
fee = _getCurrentFee(volatilityAverage);
}
}
93 changes: 93 additions & 0 deletions src/plugin/contracts/FeeDiscountPuginFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;

import './interfaces/IFeeDiscountPluginFactory.sol';
import './interfaces/plugins/IFeeDiscountPlugin.sol';
import './libraries/AdaptiveFee.sol';
import './AlgebraFeeDiscountPlugin.sol';

/// @title Algebra Integral 1.2 fee discount plugin factory
/// @notice This contract creates Algebra adaptive + discount fee plugins for Algebra liquidity pools
/// @dev This plugin factory can only be used for Algebra base pools
contract FeeDiscountPluginFactory is IFeeDiscountPluginFactory {
/// @inheritdoc IFeeDiscountPluginFactory
bytes32 public constant override ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = keccak256('ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR');

/// @inheritdoc IFeeDiscountPluginFactory
address public immutable override algebraFactory;

/// @inheritdoc IFeeDiscountPluginFactory
AlgebraFeeConfiguration public override defaultFeeConfiguration; // values of constants for sigmoids in fee calculation formula

/// @inheritdoc IFeeDiscountPluginFactory
address public override farmingAddress;

/// @inheritdoc IFeeDiscountPluginFactory
address public override feeDiscountRegistry;

/// @inheritdoc IFeeDiscountPluginFactory
mapping(address poolAddress => address pluginAddress) public override pluginByPool;

modifier onlyAdministrator() {
require(IAlgebraFactory(algebraFactory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR, msg.sender), 'Only administrator');
_;
}

constructor(address _algebraFactory) {
algebraFactory = _algebraFactory;
defaultFeeConfiguration = AdaptiveFee.initialFeeConfiguration();
emit DefaultFeeConfiguration(defaultFeeConfiguration);
}

/// @inheritdoc IAlgebraPluginFactory
function beforeCreatePoolHook(address pool, address, address, address, address, bytes calldata) external override returns (address) {
require(msg.sender == algebraFactory);
return _createPlugin(pool);
}

/// @inheritdoc IAlgebraPluginFactory
function afterCreatePoolHook(address, address, address) external view override {
require(msg.sender == algebraFactory);
}

/// @inheritdoc IFeeDiscountPluginFactory
function createPluginForExistingPool(address token0, address token1) external override returns (address) {
IAlgebraFactory factory = IAlgebraFactory(algebraFactory);
require(factory.hasRoleOrOwner(factory.POOLS_ADMINISTRATOR_ROLE(), msg.sender));

address pool = factory.poolByPair(token0, token1);
require(pool != address(0), 'Pool not exist');

return _createPlugin(pool);
}

function _createPlugin(address pool) internal returns (address) {
require(pluginByPool[pool] == address(0), 'Already created');
address plugin = address(new AlgebraFeeDiscountPlugin(pool, algebraFactory, address(this)));
IDynamicFeeManager(plugin).changeFeeConfiguration(defaultFeeConfiguration);
IFeeDiscountPlugin(plugin).setFeeDiscountRegistry(feeDiscountRegistry);
pluginByPool[pool] = plugin;
return plugin;
}

/// @inheritdoc IFeeDiscountPluginFactory
function setDefaultFeeConfiguration(AlgebraFeeConfiguration calldata newConfig) external override onlyAdministrator {
AdaptiveFee.validateFeeConfiguration(newConfig);
defaultFeeConfiguration = newConfig;
emit DefaultFeeConfiguration(newConfig);
}

/// @inheritdoc IFeeDiscountPluginFactory
function setFarmingAddress(address newFarmingAddress) external override onlyAdministrator {
require(farmingAddress != newFarmingAddress);
farmingAddress = newFarmingAddress;
emit FarmingAddress(newFarmingAddress);
}

/// @inheritdoc IFeeDiscountPluginFactory
function setFeeDiscountRegistry(address newFeeDiscountRegistry) external override onlyAdministrator {
require(feeDiscountRegistry != newFeeDiscountRegistry);
feeDiscountRegistry = newFeeDiscountRegistry;
emit FeeDiscountRegistry(newFeeDiscountRegistry);
}
}
73 changes: 73 additions & 0 deletions src/plugin/contracts/interfaces/IFeeDiscountPluginFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma abicoder v2;

import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol';

import '../base/AlgebraFeeConfiguration.sol';

/// @title The interface for the BasePluginV1Factory
/// @notice This contract creates Algebra default plugins for Algebra liquidity pools
interface IFeeDiscountPluginFactory is IAlgebraPluginFactory {
/// @notice Emitted when the default fee configuration is changed
/// @param newConfig The structure with dynamic fee parameters
/// @dev See the AdaptiveFee library for more details
event DefaultFeeConfiguration(AlgebraFeeConfiguration newConfig);

/// @notice Emitted when the farming address is changed
/// @param newFarmingAddress The farming address after the address was changed
event FarmingAddress(address newFarmingAddress);

/// @notice Emitted when the farming address is changed
/// @param newFeeDiscountRegistry The farming address after the address was changed
event FeeDiscountRegistry(address newFeeDiscountRegistry);

/// @notice The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role
/// @dev allows to change settings of BasePluginV1Factory
function ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR() external pure returns (bytes32);

/// @notice Returns the address of AlgebraFactory
/// @return The AlgebraFactory contract address
function algebraFactory() external view returns (address);

/// @notice Returns the address of fee discount registry contract
/// @return The feeDiscountRegistry contract address
function feeDiscountRegistry() external view returns (address);

/// @notice Current default dynamic fee configuration
/// @dev See the AdaptiveFee struct for more details about params.
/// This value is set by default in new plugins
function defaultFeeConfiguration()
external
view
returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee);

/// @notice Returns current farming address
/// @return The farming contract address
function farmingAddress() external view returns (address);

/// @notice Returns address of plugin created for given AlgebraPool
/// @param pool The address of AlgebraPool
/// @return The address of corresponding plugin
function pluginByPool(address pool) external view returns (address);

/// @notice Create plugin for already existing pool
/// @param token0 The address of first token in pool
/// @param token1 The address of second token in pool
/// @return The address of created plugin
function createPluginForExistingPool(address token0, address token1) external returns (address);

/// @notice Changes initial fee configuration for new pools
/// @dev changes coefficients for sigmoids: α / (1 + e^( (β-x) / γ))
/// alpha1 + alpha2 + baseFee (max possible fee) must be <= type(uint16).max and gammas must be > 0
/// @param newConfig new default fee configuration. See the #AdaptiveFee.sol library for details
function setDefaultFeeConfiguration(AlgebraFeeConfiguration calldata newConfig) external;

/// @dev updates farmings manager address on the factory
/// @param newFarmingAddress The new tokenomics contract address
function setFarmingAddress(address newFarmingAddress) external;

/// @dev updates fee discount registry address on the factory
/// @param newFeeDiscountRegistry The new FeeDiscountRegistry contract address
function setFeeDiscountRegistry(address newFeeDiscountRegistry) external;
}
10 changes: 10 additions & 0 deletions src/plugin/contracts/interfaces/plugins/IFeeDiscountPlugin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface IFeeDiscountPlugin {
function setFeeDiscountRegistry(address registry) external;

function feeDiscountRegistry() external view returns (address);

event FeeDiscountRegistry(address registry);
}
13 changes: 13 additions & 0 deletions src/plugin/contracts/interfaces/plugins/IFeeDiscountRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface IFeeDiscountRegistry {
event FeeDiscount(address user, address pool, uint16 newDiscount);

function feeDiscounts(address user, address pool) external returns (uint16 feeDiscount);
function setFeeDiscount(address user, address[] memory pools, uint16[] memory newDiscounts) external;

function algebraFactory() external view returns (address);
function FEE_DISCOUNT_MANAGER() external pure returns (bytes32);
function FEE_DISCOUNT_DENOMINATOR() external pure returns (uint16);
}
32 changes: 32 additions & 0 deletions src/plugin/contracts/plugins/FeeDiscountPlugin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;

import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol';

import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol';

import '../interfaces/plugins/IFeeDiscountPlugin.sol';
import '../interfaces/plugins/IFeeDiscountRegistry.sol';

import '../base/BasePlugin.sol';

/// @title Algebra Integral 1.2 fee discount plugin
abstract contract FeeDiscountPlugin is BasePlugin, IFeeDiscountPlugin {
using Plugins for uint8;

uint8 private constant defaultPluginConfig = uint8(Plugins.BEFORE_SWAP_FLAG);
uint16 private constant FEE_DISCOUNT_DENOMINATOR = 1000;

address public override feeDiscountRegistry;

function _applyFeeDiscount(address user, address pool, uint24 fee) internal returns (uint24 updatedFee) {
uint24 feeDiscount = IFeeDiscountRegistry(feeDiscountRegistry).feeDiscounts(user, pool);
updatedFee = uint24((uint256(fee) * (FEE_DISCOUNT_DENOMINATOR - feeDiscount)) / FEE_DISCOUNT_DENOMINATOR);
}

function setFeeDiscountRegistry(address _feeDiscountRegistry) external override {
require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender));
feeDiscountRegistry = _feeDiscountRegistry;
emit FeeDiscountRegistry(_feeDiscountRegistry);
}
}
29 changes: 29 additions & 0 deletions src/plugin/contracts/plugins/FeeDiscountRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;

import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol';
import '../interfaces/plugins/IFeeDiscountRegistry.sol';

contract FeeDiscountRegistry is IFeeDiscountRegistry {
address public immutable override algebraFactory;
bytes32 public constant override FEE_DISCOUNT_MANAGER = keccak256('FEE_DISCOUNT_MANAGER');
uint16 public constant override FEE_DISCOUNT_DENOMINATOR = 1000;

// user => pool => feeDiscount
mapping(address => mapping(address => uint16)) public override feeDiscounts;

constructor(address _algebraFactory) {
algebraFactory = _algebraFactory;
}

function setFeeDiscount(address user, address[] memory pools, uint16[] memory newDiscounts) external override {
require(IAlgebraFactory(algebraFactory).hasRoleOrOwner(FEE_DISCOUNT_MANAGER, msg.sender), 'Unauthorized');
require(pools.length == newDiscounts.length, 'ArraysLengthMismatch');

for (uint i = 0; i < pools.length; i++) {
require(newDiscounts[i] <= FEE_DISCOUNT_DENOMINATOR, 'fee discount execeeds 100%');
feeDiscounts[user][pools[i]] = newDiscounts[i];
emit FeeDiscount(user, pools[i], newDiscounts[i]);
}
}
}
6 changes: 6 additions & 0 deletions src/plugin/contracts/test/MockFactory.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.20;

import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol';

/// @title Mock of Algebra factory for plugins testing
contract MockFactory {
bytes32 public constant POOLS_ADMINISTRATOR_ROLE = keccak256('POOLS_ADMINISTRATOR');
Expand Down Expand Up @@ -31,4 +33,8 @@ contract MockFactory {
poolByPair[token0][token1] = pool;
poolByPair[token1][token0] = pool;
}

function beforeCreatePoolHook(address pluginFactory, address pool) external {
IAlgebraPluginFactory(pluginFactory).beforeCreatePoolHook(pool, address(0), address(0), address(0), address(0), '0x');
}
}
4 changes: 3 additions & 1 deletion src/plugin/contracts/test/MockPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ contract MockPool is IAlgebraPoolActions, IAlgebraPoolPermissionedActions, IAlge
mapping(bytes32 => Position) public override positions;

address owner;
uint24 public overrideFee;
uint24 public pluginFee;

/// @inheritdoc IAlgebraPoolState
function getCommunityFeePending() external pure override returns (uint128, uint128) {
Expand Down Expand Up @@ -172,7 +174,7 @@ contract MockPool is IAlgebraPoolActions, IAlgebraPoolPermissionedActions, IAlge
function swapToTick(int24 targetTick) external {
IAlgebraPlugin _plugin = IAlgebraPlugin(plugin);
if (globalState.pluginConfig & Plugins.BEFORE_SWAP_FLAG != 0) {
_plugin.beforeSwap(msg.sender, msg.sender, true, 0, 0, false, '');
(, overrideFee, pluginFee) = _plugin.beforeSwap(msg.sender, msg.sender, true, 0, 0, false, '');
}

globalState.price = TickMath.getSqrtRatioAtTick(targetTick);
Expand Down
Loading