From e2bd1dc487ad9a8cda9e128e85efdf3264959cb4 Mon Sep 17 00:00:00 2001 From: Ilia Azhel Date: Wed, 14 Jan 2026 16:48:12 +0300 Subject: [PATCH 1/4] delete plugin section --- README.md | 2 - docs/Contracts/Plugin/AlgebraBasePluginV1.md | 254 ---- docs/Contracts/Plugin/AlgebraBasePluginV2.md | 239 ---- .../Contracts/Plugin/AlgebraSecurityPlugin.md | 240 ---- docs/Contracts/Plugin/BasePluginV1Factory.md | 175 --- docs/Contracts/Plugin/BasePluginV2Factory.md | 170 --- docs/Contracts/Plugin/HydrexBasePlugin.md | 255 ---- .../Contracts/Plugin/SecurityPluginFactory.md | 146 --- .../Plugin/base/AlgebraFeeConfiguration.md | 17 - docs/Contracts/Plugin/base/BasePlugin.md | 270 ----- .../Plugin/base/BasePluginFactory.md | 85 -- .../Plugin/interfaces/IAlgebraBasePluginV1.md | 25 - .../Plugin/interfaces/IAlgebraVirtualPool.md | 36 - .../Plugin/interfaces/IBasePlugin.md | 27 - .../Plugin/interfaces/IBasePluginFactory.md | 107 -- .../Plugin/interfaces/IBasePluginV1Factory.md | 181 --- .../Plugin/interfaces/IBasePluginV2Factory.md | 167 --- .../interfaces/IHydrexBasePluginFactory.md | 301 ----- .../interfaces/ISecurityPluginFactory.md | 127 -- .../Plugin/interfaces/plugins/IAlmPlugin.md | 110 -- .../interfaces/plugins/IDynamicFeeManager.md | 66 -- .../interfaces/plugins/IFarmingPlugin.md | 97 -- .../interfaces/plugins/IRebalanceManager.md | 170 --- .../interfaces/plugins/ISecurityPlugin.md | 73 -- .../interfaces/plugins/ISecurityRegistry.md | 155 --- .../interfaces/plugins/ISlidingFeePlugin.md | 63 - .../interfaces/plugins/IVolatilityOracle.md | 161 --- .../Plugin/lens/AlgebraOracleV1TWAP.md | 183 --- .../Plugin/lens/IAlgebraOracleV1TWAP.md | 174 --- .../Plugin/libraries/VolatilityOracle.md | 21 - docs/Contracts/Plugin/plugins/AlmPlugin.md | 95 -- .../Plugin/plugins/DynamicFeePlugin.md | 48 - .../Plugin/plugins/FarmingProxyPlugin.md | 77 -- .../Plugin/plugins/SecurityPlugin.md | 40 - .../Plugin/plugins/SecurityRegistry.md | 117 -- .../Plugin/plugins/SlidingFeePlugin.md | 80 -- .../Plugin/plugins/VolatilityOraclePlugin.md | 127 -- .../types/AlgebraFeeConfigurationU144.md | 4 - package.json | 4 +- scripts/deployAll.js | 2 - scripts/verifyAll.js | 2 - src/README.md | 4 - src/plugin/.eslintrc.cjs | 5 - src/plugin/.gitignore | 10 - src/plugin/.solcover.js | 13 - src/plugin/.solhintignore | 1 - src/plugin/LICENSE | 98 -- src/plugin/README.md | 15 - src/plugin/contracts/AlgebraBasePluginV1.sol | 80 -- src/plugin/contracts/AlgebraBasePluginV2.sol | 85 -- src/plugin/contracts/BasePluginV1Factory.sol | 80 -- src/plugin/contracts/BasePluginV2Factory.sol | 78 -- src/plugin/contracts/README.md | 7 - .../contracts/base/AlgebraBasePlugin.sol | 18 - .../base/AlgebraFeeConfiguration.sol | 14 - src/plugin/contracts/base/BasePlugin.sol | 134 --- .../contracts/base/BasePluginFactory.sol | 31 - .../interfaces/IAlgebraVirtualPool.sol | 12 - .../contracts/interfaces/IBasePlugin.sol | 14 - .../interfaces/IBasePluginFactory.sol | 41 - .../interfaces/IBasePluginV1Factory.sol | 61 - .../interfaces/IBasePluginV2Factory.sol | 46 - .../interfaces/plugins/IDynamicFeeManager.sol | 22 - .../interfaces/plugins/IFarmingPlugin.sol | 32 - .../interfaces/plugins/ISlidingFeePlugin.sol | 11 - .../interfaces/plugins/IVolatilityOracle.sol | 71 -- .../contracts/lens/AlgebraOracleV1TWAP.sol | 71 -- .../contracts/lens/IAlgebraOracleV1TWAP.sol | 45 - .../contracts/libraries/AdaptiveFee.sol | 134 --- .../contracts/libraries/VolatilityOracle.sol | 556 --------- .../libraries/integration/OracleLibrary.sol | 113 -- .../contracts/plugins/DynamicFeePlugin.sol | 63 - .../contracts/plugins/FarmingProxyPlugin.sol | 85 -- .../contracts/plugins/SlidingFeePlugin.sol | 111 -- .../plugins/VolatilityOraclePlugin.sol | 117 -- src/plugin/contracts/test/AdaptiveFeeTest.sol | 41 - src/plugin/contracts/test/MockFactory.sol | 34 - src/plugin/contracts/test/MockObservable.sol | 71 -- src/plugin/contracts/test/MockPool.sol | 253 ---- .../test/MockTimeAlgebraBasePluginV1.sol | 73 -- .../test/MockTimeAlgebraBasePluginV2.sol | 73 -- .../contracts/test/MockTimeDSFactory.sol | 76 -- .../contracts/test/MockTimeDSFactoryV2.sol | 72 -- .../contracts/test/MockTimeVirtualPool.sol | 24 - .../contracts/test/OracleLibraryTest.sol | 43 - .../contracts/test/SimulationAdaptiveFee.sol | 48 - src/plugin/contracts/test/SlidingFeeTest.sol | 31 - src/plugin/contracts/test/TestERC20.sol | 76 -- src/plugin/contracts/test/TestVirtualPool.sol | 23 - .../contracts/test/VolatilityOracleTest.sol | 217 ---- .../test/echidna/AdaptiveFeeEchidnaTest.sol | 67 -- .../echidna/VolatilityOracleEchidnaTest.sol | 130 -- .../VolatilityOracleMathEchidnaTest.sol | 29 - .../contracts/test/echidna/echidna.config.yml | 75 -- .../types/AlgebraFeeConfigurationU144.sol | 79 -- src/plugin/echidna.config.yml | 74 -- src/plugin/hardhat.config.ts | 89 -- src/plugin/package-lock.json | 55 - src/plugin/package.json | 44 - src/plugin/scripts/deploy.js | 34 - src/plugin/scripts/simulation/.gitignore | 2 - .../simulation/AdaptiveFeeSimulation.ts | 98 -- src/plugin/scripts/simulation/README.md | 5 - .../scripts/simulation/external-data/.gitkeep | 0 src/plugin/scripts/verify.js | 28 - src/plugin/scripts/verifyPlugin.js | 40 - src/plugin/slither.config.json | 4 - src/plugin/test/AdaptiveFee.spec.ts | 111 -- src/plugin/test/AlgebraBasePluginV1.spec.ts | 780 ------------ src/plugin/test/AlgebraBasePluginV2.spec.ts | 577 --------- src/plugin/test/AlgebraOracleV1TWAP.spec.ts | 222 ---- src/plugin/test/AlgebraPool.gas.spec.ts | 527 --------- src/plugin/test/BasePluginV1Factory.spec.ts | 165 --- src/plugin/test/BasePluginV2Factory.spec.ts | 125 -- src/plugin/test/OracleLibrary.spec.ts | 204 ---- src/plugin/test/SlidingFee.spec.ts | 253 ---- src/plugin/test/VolatilityOracle.spec.ts | 1054 ----------------- .../__snapshots__/AdaptiveFee.spec.ts.snap | 48 - .../AlgebraBasePluginV1.spec.ts.snap | 123 -- .../AlgebraPool.gas.spec.ts.snap | 143 --- .../__snapshots__/OracleLibrary.spec.ts.snap | 5 - .../__snapshots__/SlidingFee.spec.ts.snap | 7 - .../VolatilityOracle.spec.ts.snap | 101 -- .../test/shared/checkTimepointEquals.ts | 32 - src/plugin/test/shared/expect.ts | 6 - src/plugin/test/shared/externalFixtures.ts | 111 -- src/plugin/test/shared/fixtures.ts | 102 -- src/plugin/test/shared/format.ts | 10 - src/plugin/test/shared/snapshotGasCost.ts | 27 - src/plugin/test/shared/utilities.ts | 70 -- src/plugin/tsconfig.json | 17 - 131 files changed, 2 insertions(+), 13797 deletions(-) delete mode 100644 docs/Contracts/Plugin/AlgebraBasePluginV1.md delete mode 100644 docs/Contracts/Plugin/AlgebraBasePluginV2.md delete mode 100644 docs/Contracts/Plugin/AlgebraSecurityPlugin.md delete mode 100644 docs/Contracts/Plugin/BasePluginV1Factory.md delete mode 100644 docs/Contracts/Plugin/BasePluginV2Factory.md delete mode 100644 docs/Contracts/Plugin/HydrexBasePlugin.md delete mode 100644 docs/Contracts/Plugin/SecurityPluginFactory.md delete mode 100644 docs/Contracts/Plugin/base/AlgebraFeeConfiguration.md delete mode 100644 docs/Contracts/Plugin/base/BasePlugin.md delete mode 100644 docs/Contracts/Plugin/base/BasePluginFactory.md delete mode 100644 docs/Contracts/Plugin/interfaces/IAlgebraBasePluginV1.md delete mode 100644 docs/Contracts/Plugin/interfaces/IAlgebraVirtualPool.md delete mode 100644 docs/Contracts/Plugin/interfaces/IBasePlugin.md delete mode 100644 docs/Contracts/Plugin/interfaces/IBasePluginFactory.md delete mode 100644 docs/Contracts/Plugin/interfaces/IBasePluginV1Factory.md delete mode 100644 docs/Contracts/Plugin/interfaces/IBasePluginV2Factory.md delete mode 100644 docs/Contracts/Plugin/interfaces/IHydrexBasePluginFactory.md delete mode 100644 docs/Contracts/Plugin/interfaces/ISecurityPluginFactory.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/IAlmPlugin.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/IDynamicFeeManager.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/IFarmingPlugin.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/IRebalanceManager.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/ISecurityPlugin.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/ISecurityRegistry.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/ISlidingFeePlugin.md delete mode 100644 docs/Contracts/Plugin/interfaces/plugins/IVolatilityOracle.md delete mode 100644 docs/Contracts/Plugin/lens/AlgebraOracleV1TWAP.md delete mode 100644 docs/Contracts/Plugin/lens/IAlgebraOracleV1TWAP.md delete mode 100644 docs/Contracts/Plugin/libraries/VolatilityOracle.md delete mode 100644 docs/Contracts/Plugin/plugins/AlmPlugin.md delete mode 100644 docs/Contracts/Plugin/plugins/DynamicFeePlugin.md delete mode 100644 docs/Contracts/Plugin/plugins/FarmingProxyPlugin.md delete mode 100644 docs/Contracts/Plugin/plugins/SecurityPlugin.md delete mode 100644 docs/Contracts/Plugin/plugins/SecurityRegistry.md delete mode 100644 docs/Contracts/Plugin/plugins/SlidingFeePlugin.md delete mode 100644 docs/Contracts/Plugin/plugins/VolatilityOraclePlugin.md delete mode 100644 docs/Contracts/Plugin/types/AlgebraFeeConfigurationU144.md delete mode 100644 src/plugin/.eslintrc.cjs delete mode 100644 src/plugin/.gitignore delete mode 100644 src/plugin/.solcover.js delete mode 100644 src/plugin/.solhintignore delete mode 100644 src/plugin/LICENSE delete mode 100644 src/plugin/README.md delete mode 100644 src/plugin/contracts/AlgebraBasePluginV1.sol delete mode 100644 src/plugin/contracts/AlgebraBasePluginV2.sol delete mode 100644 src/plugin/contracts/BasePluginV1Factory.sol delete mode 100644 src/plugin/contracts/BasePluginV2Factory.sol delete mode 100644 src/plugin/contracts/README.md delete mode 100644 src/plugin/contracts/base/AlgebraBasePlugin.sol delete mode 100644 src/plugin/contracts/base/AlgebraFeeConfiguration.sol delete mode 100644 src/plugin/contracts/base/BasePlugin.sol delete mode 100644 src/plugin/contracts/base/BasePluginFactory.sol delete mode 100644 src/plugin/contracts/interfaces/IAlgebraVirtualPool.sol delete mode 100644 src/plugin/contracts/interfaces/IBasePlugin.sol delete mode 100644 src/plugin/contracts/interfaces/IBasePluginFactory.sol delete mode 100644 src/plugin/contracts/interfaces/IBasePluginV1Factory.sol delete mode 100644 src/plugin/contracts/interfaces/IBasePluginV2Factory.sol delete mode 100644 src/plugin/contracts/interfaces/plugins/IDynamicFeeManager.sol delete mode 100644 src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol delete mode 100644 src/plugin/contracts/interfaces/plugins/ISlidingFeePlugin.sol delete mode 100644 src/plugin/contracts/interfaces/plugins/IVolatilityOracle.sol delete mode 100644 src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol delete mode 100644 src/plugin/contracts/lens/IAlgebraOracleV1TWAP.sol delete mode 100644 src/plugin/contracts/libraries/AdaptiveFee.sol delete mode 100644 src/plugin/contracts/libraries/VolatilityOracle.sol delete mode 100644 src/plugin/contracts/libraries/integration/OracleLibrary.sol delete mode 100644 src/plugin/contracts/plugins/DynamicFeePlugin.sol delete mode 100644 src/plugin/contracts/plugins/FarmingProxyPlugin.sol delete mode 100644 src/plugin/contracts/plugins/SlidingFeePlugin.sol delete mode 100644 src/plugin/contracts/plugins/VolatilityOraclePlugin.sol delete mode 100644 src/plugin/contracts/test/AdaptiveFeeTest.sol delete mode 100644 src/plugin/contracts/test/MockFactory.sol delete mode 100644 src/plugin/contracts/test/MockObservable.sol delete mode 100644 src/plugin/contracts/test/MockPool.sol delete mode 100644 src/plugin/contracts/test/MockTimeAlgebraBasePluginV1.sol delete mode 100644 src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol delete mode 100644 src/plugin/contracts/test/MockTimeDSFactory.sol delete mode 100644 src/plugin/contracts/test/MockTimeDSFactoryV2.sol delete mode 100644 src/plugin/contracts/test/MockTimeVirtualPool.sol delete mode 100644 src/plugin/contracts/test/OracleLibraryTest.sol delete mode 100644 src/plugin/contracts/test/SimulationAdaptiveFee.sol delete mode 100644 src/plugin/contracts/test/SlidingFeeTest.sol delete mode 100644 src/plugin/contracts/test/TestERC20.sol delete mode 100644 src/plugin/contracts/test/TestVirtualPool.sol delete mode 100644 src/plugin/contracts/test/VolatilityOracleTest.sol delete mode 100644 src/plugin/contracts/test/echidna/AdaptiveFeeEchidnaTest.sol delete mode 100644 src/plugin/contracts/test/echidna/VolatilityOracleEchidnaTest.sol delete mode 100644 src/plugin/contracts/test/echidna/VolatilityOracleMathEchidnaTest.sol delete mode 100644 src/plugin/contracts/test/echidna/echidna.config.yml delete mode 100644 src/plugin/contracts/types/AlgebraFeeConfigurationU144.sol delete mode 100644 src/plugin/echidna.config.yml delete mode 100644 src/plugin/hardhat.config.ts delete mode 100644 src/plugin/package-lock.json delete mode 100644 src/plugin/package.json delete mode 100644 src/plugin/scripts/deploy.js delete mode 100644 src/plugin/scripts/simulation/.gitignore delete mode 100644 src/plugin/scripts/simulation/AdaptiveFeeSimulation.ts delete mode 100644 src/plugin/scripts/simulation/README.md delete mode 100644 src/plugin/scripts/simulation/external-data/.gitkeep delete mode 100644 src/plugin/scripts/verify.js delete mode 100644 src/plugin/scripts/verifyPlugin.js delete mode 100644 src/plugin/slither.config.json delete mode 100644 src/plugin/test/AdaptiveFee.spec.ts delete mode 100644 src/plugin/test/AlgebraBasePluginV1.spec.ts delete mode 100644 src/plugin/test/AlgebraBasePluginV2.spec.ts delete mode 100644 src/plugin/test/AlgebraOracleV1TWAP.spec.ts delete mode 100644 src/plugin/test/AlgebraPool.gas.spec.ts delete mode 100644 src/plugin/test/BasePluginV1Factory.spec.ts delete mode 100644 src/plugin/test/BasePluginV2Factory.spec.ts delete mode 100644 src/plugin/test/OracleLibrary.spec.ts delete mode 100644 src/plugin/test/SlidingFee.spec.ts delete mode 100644 src/plugin/test/VolatilityOracle.spec.ts delete mode 100644 src/plugin/test/__snapshots__/AdaptiveFee.spec.ts.snap delete mode 100644 src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap delete mode 100644 src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap delete mode 100644 src/plugin/test/__snapshots__/OracleLibrary.spec.ts.snap delete mode 100644 src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap delete mode 100644 src/plugin/test/__snapshots__/VolatilityOracle.spec.ts.snap delete mode 100644 src/plugin/test/shared/checkTimepointEquals.ts delete mode 100644 src/plugin/test/shared/expect.ts delete mode 100644 src/plugin/test/shared/externalFixtures.ts delete mode 100644 src/plugin/test/shared/fixtures.ts delete mode 100644 src/plugin/test/shared/format.ts delete mode 100644 src/plugin/test/shared/snapshotGasCost.ts delete mode 100644 src/plugin/test/shared/utilities.ts delete mode 100644 src/plugin/tsconfig.json diff --git a/README.md b/README.md index e9a202ccb..5a69aa034 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,6 @@ Periphery: [https://www.npmjs.com/package/@cryptoalgebra/integral-periphery](htt Farming: [https://www.npmjs.com/package/@cryptoalgebra/integral-farming](https://www.npmjs.com/package/@cryptoalgebra/integral-farming) -Basic plugin: [https://www.npmjs.com/package/@cryptoalgebra/integral-base-plugin](https://www.npmjs.com/package/@cryptoalgebra/integral-base-plugin) - ## Build *Requires npm >= 8.0.0* diff --git a/docs/Contracts/Plugin/AlgebraBasePluginV1.md b/docs/Contracts/Plugin/AlgebraBasePluginV1.md deleted file mode 100644 index c0ab77aba..000000000 --- a/docs/Contracts/Plugin/AlgebraBasePluginV1.md +++ /dev/null @@ -1,254 +0,0 @@ - - -# AlgebraBasePluginV1 - - -Algebra Integral 1.2.2 adaptive fee plugin - - - -**Inherits:** [DynamicFeePlugin](plugins/DynamicFeePlugin.md) [FarmingProxyPlugin](plugins/FarmingProxyPlugin.md) [VolatilityOraclePlugin](plugins/VolatilityOraclePlugin.md) - -## Public variables -### defaultPluginConfig -```solidity -uint8 constant defaultPluginConfig -``` -**Selector**: `0x689ea370` - -Returns plugin config - - - -## Functions -### constructor - -```solidity -constructor(address _pool, address _factory, address _pluginFactory, struct AlgebraFeeConfiguration _config) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _pool | address | | -| _factory | address | | -| _pluginFactory | address | | -| _config | struct AlgebraFeeConfiguration | | - -### beforeInitialize - -```solidity -function beforeInitialize(address, uint160) external returns (bytes4) -``` -**Selector**: `0x636fd804` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterInitialize - -```solidity -function afterInitialize(address, uint160, int24 tick) external returns (bytes4) -``` -**Selector**: `0x82dd6522` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | -| tick | int24 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeModifyPosition - -```solidity -function beforeModifyPosition(address, address, int24, int24, int128, bytes) external returns (bytes4, uint24) -``` -**Selector**: `0x5e2411b2` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | - -### afterModifyPosition - -```solidity -function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0xd6852010` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeSwap - -```solidity -function beforeSwap(address, address, bool, int256, uint160, bool, bytes) external returns (bytes4, uint24, uint24) -``` -**Selector**: `0x029c1cb7` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | bool | | -| | int256 | | -| | uint160 | | -| | bool | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | -| [2] | uint24 | | - -### afterSwap - -```solidity -function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes) external returns (bytes4) -``` -**Selector**: `0x9cb5a963` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| zeroToOne | bool | | -| | int256 | | -| | uint160 | | -| | int256 | | -| | int256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeFlash - -```solidity -function beforeFlash(address, address, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x8de0a8ee` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterFlash - -```solidity -function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x343d37ff` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### getCurrentFee - -```solidity -function getCurrentFee() external view returns (uint16 fee) -``` -**Selector**: `0xf70d9362` - -Returns fee from plugin - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| fee | uint16 | The pool fee value in hundredths of a bip, i.e. 1e-6 | - diff --git a/docs/Contracts/Plugin/AlgebraBasePluginV2.md b/docs/Contracts/Plugin/AlgebraBasePluginV2.md deleted file mode 100644 index b07498054..000000000 --- a/docs/Contracts/Plugin/AlgebraBasePluginV2.md +++ /dev/null @@ -1,239 +0,0 @@ - - -# AlgebraBasePluginV2 - - -Algebra Integral 1.2.2 sliding fee plugin - - - -**Inherits:** [SlidingFeePlugin](plugins/SlidingFeePlugin.md) [FarmingProxyPlugin](plugins/FarmingProxyPlugin.md) [VolatilityOraclePlugin](plugins/VolatilityOraclePlugin.md) - -## Public variables -### defaultPluginConfig -```solidity -uint8 constant defaultPluginConfig -``` -**Selector**: `0x689ea370` - -Returns plugin config - - - -## Functions -### constructor - -```solidity -constructor(address _pool, address _factory, address _pluginFactory, uint16 _baseFee) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _pool | address | | -| _factory | address | | -| _pluginFactory | address | | -| _baseFee | uint16 | | - -### beforeInitialize - -```solidity -function beforeInitialize(address, uint160) external returns (bytes4) -``` -**Selector**: `0x636fd804` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterInitialize - -```solidity -function afterInitialize(address, uint160, int24 tick) external returns (bytes4) -``` -**Selector**: `0x82dd6522` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | -| tick | int24 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeModifyPosition - -```solidity -function beforeModifyPosition(address, address, int24, int24, int128, bytes) external returns (bytes4, uint24) -``` -**Selector**: `0x5e2411b2` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | - -### afterModifyPosition - -```solidity -function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0xd6852010` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeSwap - -```solidity -function beforeSwap(address, address, bool zeroToOne, int256, uint160, bool, bytes) external returns (bytes4, uint24, uint24) -``` -**Selector**: `0x029c1cb7` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| zeroToOne | bool | | -| | int256 | | -| | uint160 | | -| | bool | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | -| [2] | uint24 | | - -### afterSwap - -```solidity -function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes) external returns (bytes4) -``` -**Selector**: `0x9cb5a963` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| zeroToOne | bool | | -| | int256 | | -| | uint160 | | -| | int256 | | -| | int256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeFlash - -```solidity -function beforeFlash(address, address, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x8de0a8ee` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterFlash - -```solidity -function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x343d37ff` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - diff --git a/docs/Contracts/Plugin/AlgebraSecurityPlugin.md b/docs/Contracts/Plugin/AlgebraSecurityPlugin.md deleted file mode 100644 index e48085424..000000000 --- a/docs/Contracts/Plugin/AlgebraSecurityPlugin.md +++ /dev/null @@ -1,240 +0,0 @@ - - -# AlgebraSecurityPlugin - - -Algebra Integral 1.2.1 security plugin - - - -**Inherits:** [SecurityPlugin](plugins/SecurityPlugin.md) - -## Public variables -### defaultPluginConfig -```solidity -uint8 constant defaultPluginConfig -``` -**Selector**: `0x689ea370` - -Returns plugin config - - - -## Functions -### constructor - -```solidity -constructor(address _pool, address _factory, address _pluginFactory) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _pool | address | | -| _factory | address | | -| _pluginFactory | address | | - -### beforeInitialize - -```solidity -function beforeInitialize(address, uint160) external returns (bytes4) -``` -**Selector**: `0x636fd804` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterInitialize - -```solidity -function afterInitialize(address, uint160, int24) external returns (bytes4) -``` -**Selector**: `0x82dd6522` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | -| | int24 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeModifyPosition - -```solidity -function beforeModifyPosition(address, address, int24, int24, int128 liquidity, bytes) external returns (bytes4, uint24) -``` -**Selector**: `0x5e2411b2` - -@dev - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| liquidity | int128 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | - -### afterModifyPosition - -```solidity -function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0xd6852010` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeSwap - -```solidity -function beforeSwap(address, address, bool, int256, uint160, bool, bytes) external returns (bytes4, uint24, uint24) -``` -**Selector**: `0x029c1cb7` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | bool | | -| | int256 | | -| | uint160 | | -| | bool | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | -| [2] | uint24 | | - -### afterSwap - -```solidity -function afterSwap(address, address, bool, int256, uint160, int256, int256, bytes) external returns (bytes4) -``` -**Selector**: `0x9cb5a963` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | bool | | -| | int256 | | -| | uint160 | | -| | int256 | | -| | int256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeFlash - -```solidity -function beforeFlash(address, address, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x8de0a8ee` - -@dev - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterFlash - -```solidity -function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x343d37ff` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - diff --git a/docs/Contracts/Plugin/BasePluginV1Factory.md b/docs/Contracts/Plugin/BasePluginV1Factory.md deleted file mode 100644 index 34c1bca44..000000000 --- a/docs/Contracts/Plugin/BasePluginV1Factory.md +++ /dev/null @@ -1,175 +0,0 @@ - - -# BasePluginV1Factory - - -Algebra Integral 1.2.2 default plugin factory - -This contract creates Algebra adaptive fee plugins for Algebra liquidity pools - -*Developer note: This plugin factory can only be used for Algebra base pools* - -**Inherits:** [IBasePluginV1Factory](interfaces/IBasePluginV1Factory.md) -## Modifiers -### onlyAdministrator - -```solidity -modifier onlyAdministrator() -``` - - - - -## Public variables -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR -```solidity -bytes32 constant ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = 0x267da724c255813ae00f4522fe843cb70148a4b8099cbc5af64f9a4151e55ed6 -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV1Factory* - -### algebraFactory -```solidity -address immutable algebraFactory -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - - -### defaultFeeConfiguration -```solidity -struct AlgebraFeeConfiguration defaultFeeConfiguration -``` -**Selector**: `0x4e09a96a` - -Current default dynamic fee configuration - -*Developer note: See the AdaptiveFee struct for more details about params. -This value is set by default in new plugins* - -### farmingAddress -```solidity -address farmingAddress -``` -**Selector**: `0x8a2ade58` - -Returns current farming address - - -### pluginByPool -```solidity -mapping(address => address) pluginByPool -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - - - -## Functions -### constructor - -```solidity -constructor(address _algebraFactory) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _algebraFactory | address | | - -### beforeCreatePoolHook - -```solidity -function beforeCreatePoolHook(address pool, address, address, address, address, bytes) external returns (address) -``` -**Selector**: `0x1d0338d9` - -Deploys new plugin contract for pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of the new pool | -| | address | | -| | address | | -| | address | | -| | address | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | New plugin address | - -### afterCreatePoolHook - -```solidity -function afterCreatePoolHook(address, address, address) external view -``` -**Selector**: `0x8d5ef8d1` - -Called after the pool is created - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | address | | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setDefaultFeeConfiguration - -```solidity -function setDefaultFeeConfiguration(struct AlgebraFeeConfiguration newConfig) external -``` -**Selector**: `0xf718949a` - -Changes initial fee configuration for new pools - -*Developer note: changes coefficients for sigmoids: α / (1 + e^( (β-x) / γ)) -alpha1 + alpha2 + baseFee (max possible fee) must be <= type(uint16).max and gammas must be > 0* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newConfig | struct AlgebraFeeConfiguration | new default fee configuration. See the #AdaptiveFee.sol library for details | - -### setFarmingAddress - -```solidity -function setFarmingAddress(address newFarmingAddress) external -``` -**Selector**: `0xb001f618` - - - -*Developer note: updates farmings manager address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newFarmingAddress | address | The new tokenomics contract address | - diff --git a/docs/Contracts/Plugin/BasePluginV2Factory.md b/docs/Contracts/Plugin/BasePluginV2Factory.md deleted file mode 100644 index bc0001d38..000000000 --- a/docs/Contracts/Plugin/BasePluginV2Factory.md +++ /dev/null @@ -1,170 +0,0 @@ - - -# BasePluginV2Factory - - -Algebra Integral 1.2.2 default plugin factory - -This contract creates Algebra sliding fee plugins for Algebra liquidity pools - -*Developer note: This plugin factory can only be used for Algebra base pools* - -**Inherits:** [IBasePluginV2Factory](interfaces/IBasePluginV2Factory.md) -## Modifiers -### onlyAdministrator - -```solidity -modifier onlyAdministrator() -``` - - - - -## Public variables -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR -```solidity -bytes32 constant ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = 0x267da724c255813ae00f4522fe843cb70148a4b8099cbc5af64f9a4151e55ed6 -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV2Factory* - -### algebraFactory -```solidity -address immutable algebraFactory -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - - -### farmingAddress -```solidity -address farmingAddress -``` -**Selector**: `0x8a2ade58` - -Returns current farming address - - -### defaultBaseFee -```solidity -uint16 defaultBaseFee -``` -**Selector**: `0x675ec3d7` - - - - -### pluginByPool -```solidity -mapping(address => address) pluginByPool -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - - - -## Functions -### constructor - -```solidity -constructor(address _algebraFactory) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _algebraFactory | address | | - -### beforeCreatePoolHook - -```solidity -function beforeCreatePoolHook(address pool, address, address, address, address, bytes) external returns (address) -``` -**Selector**: `0x1d0338d9` - -Deploys new plugin contract for pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of the new pool | -| | address | | -| | address | | -| | address | | -| | address | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | New plugin address | - -### afterCreatePoolHook - -```solidity -function afterCreatePoolHook(address, address, address) external view -``` -**Selector**: `0x8d5ef8d1` - -Called after the pool is created - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | address | | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setFarmingAddress - -```solidity -function setFarmingAddress(address newFarmingAddress) external -``` -**Selector**: `0xb001f618` - - - -*Developer note: updates farmings manager address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newFarmingAddress | address | The new tokenomics contract address | - -### setDefaultBaseFee - -```solidity -function setDefaultBaseFee(uint16 newDefaultBaseFee) external -``` -**Selector**: `0x0bc614c4` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newDefaultBaseFee | uint16 | | - diff --git a/docs/Contracts/Plugin/HydrexBasePlugin.md b/docs/Contracts/Plugin/HydrexBasePlugin.md deleted file mode 100644 index 39833a24d..000000000 --- a/docs/Contracts/Plugin/HydrexBasePlugin.md +++ /dev/null @@ -1,255 +0,0 @@ - - -# HydrexBasePlugin - - -Algebra Integral 1.2.1 plugin. Contains adaptive + sliding fee, safety switch and twap oracle - - - -**Inherits:** [DynamicFeePlugin](plugins/DynamicFeePlugin.md) [VolatilityOraclePlugin](plugins/VolatilityOraclePlugin.md) [SlidingFeePlugin](plugins/SlidingFeePlugin.md) [SecurityPlugin](plugins/SecurityPlugin.md) [AlmPlugin](plugins/AlmPlugin.md) - -## Public variables -### defaultPluginConfig -```solidity -uint8 constant defaultPluginConfig -``` -**Selector**: `0x689ea370` - -Returns plugin config - - - -## Functions -### constructor - -```solidity -constructor(address _pool, address _factory, address _pluginFactory, struct AlgebraFeeConfiguration _config, uint16 _baseFee) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _pool | address | | -| _factory | address | | -| _pluginFactory | address | | -| _config | struct AlgebraFeeConfiguration | | -| _baseFee | uint16 | | - -### beforeInitialize - -```solidity -function beforeInitialize(address, uint160) external returns (bytes4) -``` -**Selector**: `0x636fd804` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterInitialize - -```solidity -function afterInitialize(address, uint160, int24 tick) external returns (bytes4) -``` -**Selector**: `0x82dd6522` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | -| tick | int24 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeModifyPosition - -```solidity -function beforeModifyPosition(address, address, int24, int24, int128 liquidity, bytes) external returns (bytes4, uint24) -``` -**Selector**: `0x5e2411b2` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| liquidity | int128 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | - -### afterModifyPosition - -```solidity -function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0xd6852010` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeSwap - -```solidity -function beforeSwap(address, address, bool zeroToOne, int256, uint160, bool, bytes) external returns (bytes4, uint24, uint24) -``` -**Selector**: `0x029c1cb7` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| zeroToOne | bool | | -| | int256 | | -| | uint160 | | -| | bool | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | -| [2] | uint24 | | - -### afterSwap - -```solidity -function afterSwap(address, address, bool, int256, uint160, int256, int256, bytes) external returns (bytes4) -``` -**Selector**: `0x9cb5a963` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | bool | | -| | int256 | | -| | uint160 | | -| | int256 | | -| | int256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeFlash - -```solidity -function beforeFlash(address, address, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x8de0a8ee` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterFlash - -```solidity -function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes) external returns (bytes4) -``` -**Selector**: `0x343d37ff` - - - -*Developer note: unused* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### getCurrentFee - -```solidity -function getCurrentFee() external view returns (uint16 fee) -``` -**Selector**: `0xf70d9362` - -Returns fee from plugin - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| fee | uint16 | The pool fee value in hundredths of a bip, i.e. 1e-6 | - diff --git a/docs/Contracts/Plugin/SecurityPluginFactory.md b/docs/Contracts/Plugin/SecurityPluginFactory.md deleted file mode 100644 index 78cdaab04..000000000 --- a/docs/Contracts/Plugin/SecurityPluginFactory.md +++ /dev/null @@ -1,146 +0,0 @@ - - -# SecurityPluginFactory - - -Algebra Integral 1.2.1 security plugin factory - - - -**Inherits:** [ISecurityPluginFactory](interfaces/ISecurityPluginFactory.md) -## Modifiers -### onlyAdministrator - -```solidity -modifier onlyAdministrator() -``` - - - - -## Public variables -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR -```solidity -bytes32 constant ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = 0x267da724c255813ae00f4522fe843cb70148a4b8099cbc5af64f9a4151e55ed6 -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV1Factory* - -### algebraFactory -```solidity -address immutable algebraFactory -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - - -### securityRegistry -```solidity -address securityRegistry -``` -**Selector**: `0x9b21f9ae` - -Returns current securityRegistry address - - -### pluginByPool -```solidity -mapping(address => address) pluginByPool -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - - - -## Functions -### constructor - -```solidity -constructor(address _algebraFactory) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _algebraFactory | address | | - -### beforeCreatePoolHook - -```solidity -function beforeCreatePoolHook(address pool, address, address, address, address, bytes) external returns (address) -``` -**Selector**: `0x1d0338d9` - -Deploys new plugin contract for pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of the new pool | -| | address | | -| | address | | -| | address | | -| | address | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | New plugin address | - -### afterCreatePoolHook - -```solidity -function afterCreatePoolHook(address, address, address) external view -``` -**Selector**: `0x8d5ef8d1` - -Called after the pool is created - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | address | | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setSecurityRegistry - -```solidity -function setSecurityRegistry(address _securityRegistry) external -``` -**Selector**: `0x64fae8a9` - - - -*Developer note: updates securoty registry address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _securityRegistry | address | | - diff --git a/docs/Contracts/Plugin/base/AlgebraFeeConfiguration.md b/docs/Contracts/Plugin/base/AlgebraFeeConfiguration.md deleted file mode 100644 index 8e96652fb..000000000 --- a/docs/Contracts/Plugin/base/AlgebraFeeConfiguration.md +++ /dev/null @@ -1,17 +0,0 @@ -# AlgebraFeeConfiguration - -coefficients for sigmoids: α / (1 + e^( (β-x) / γ)) - -*Developer note: alpha1 + alpha2 + baseFee must be <= type(uint16).max* - -```solidity -struct AlgebraFeeConfiguration { - uint16 alpha1; - uint16 alpha2; - uint32 beta1; - uint32 beta2; - uint16 gamma1; - uint16 gamma2; - uint16 baseFee; -} -``` diff --git a/docs/Contracts/Plugin/base/BasePlugin.md b/docs/Contracts/Plugin/base/BasePlugin.md deleted file mode 100644 index 96420caea..000000000 --- a/docs/Contracts/Plugin/base/BasePlugin.md +++ /dev/null @@ -1,270 +0,0 @@ - - -# BasePlugin - - -Algebra Integral 1.2.2 plugin base - -This contract simplifies development process of plugins by providing base functionality - -**Inherits:** [IBasePlugin](../interfaces/IBasePlugin.md) Timestamp -## Modifiers -### onlyPool - -```solidity -modifier onlyPool() -``` - - - - -## Public variables -### ALGEBRA_BASE_PLUGIN_MANAGER -```solidity -bytes32 constant ALGEBRA_BASE_PLUGIN_MANAGER = 0x8e8000aba5b365c0be9685da1153f7f096e76d1ecfb42c050ae1e387aa65b4f5 -``` -**Selector**: `0x31b25d1a` - - - -*Developer note: The role can be granted in AlgebraFactory* - -### pool -```solidity -address immutable pool -``` -**Selector**: `0x16f0115b` - - - - - -## Functions -### collectPluginFee - -```solidity -function collectPluginFee(address token, uint256 amount, address recipient) external -``` -**Selector**: `0xe72c652d` - -Claim plugin fee - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token | address | The token address | -| amount | uint256 | Amount of tokens | -| recipient | address | Recipient address | - -### handlePluginFee - -```solidity -function handlePluginFee(uint256, uint256) external view returns (bytes4) -``` -**Selector**: `0xaa6b14bb` - -Handle plugin fee transfer on plugin contract - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | uint256 | | -| | uint256 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | bytes4 The function selector | - -### beforeInitialize - -```solidity -function beforeInitialize(address, uint160) external virtual returns (bytes4) -``` -**Selector**: `0x636fd804` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterInitialize - -```solidity -function afterInitialize(address, uint160, int24) external virtual returns (bytes4) -``` -**Selector**: `0x82dd6522` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | uint160 | | -| | int24 | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeModifyPosition - -```solidity -function beforeModifyPosition(address, address, int24, int24, int128, bytes) external virtual returns (bytes4, uint24) -``` -**Selector**: `0x5e2411b2` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | - -### afterModifyPosition - -```solidity -function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes) external virtual returns (bytes4) -``` -**Selector**: `0xd6852010` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | int24 | | -| | int24 | | -| | int128 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeSwap - -```solidity -function beforeSwap(address, address, bool, int256, uint160, bool, bytes) external virtual returns (bytes4, uint24, uint24) -``` -**Selector**: `0x029c1cb7` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | bool | | -| | int256 | | -| | uint160 | | -| | bool | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | -| [1] | uint24 | | -| [2] | uint24 | | - -### afterSwap - -```solidity -function afterSwap(address, address, bool, int256, uint160, int256, int256, bytes) external virtual returns (bytes4) -``` -**Selector**: `0x9cb5a963` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | bool | | -| | int256 | | -| | uint160 | | -| | int256 | | -| | int256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### beforeFlash - -```solidity -function beforeFlash(address, address, uint256, uint256, bytes) external virtual returns (bytes4) -``` -**Selector**: `0x8de0a8ee` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - -### afterFlash - -```solidity -function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes) external virtual returns (bytes4) -``` -**Selector**: `0x343d37ff` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | uint256 | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes4 | | - diff --git a/docs/Contracts/Plugin/base/BasePluginFactory.md b/docs/Contracts/Plugin/base/BasePluginFactory.md deleted file mode 100644 index cf2c96ae6..000000000 --- a/docs/Contracts/Plugin/base/BasePluginFactory.md +++ /dev/null @@ -1,85 +0,0 @@ - - -# BasePluginFactory - - - - - - -**Inherits:** [IBasePluginFactory](../interfaces/IBasePluginFactory.md) - -## Public variables -### entryPoint -```solidity -address immutable entryPoint -``` -**Selector**: `0xb0d691fe` - -Returns the address of AlgebraCustomPoolEntryPoint - -*Developer note: This is a main entry point for creating, managing plugins* - - -## Functions -### createCustomPool - -```solidity -function createCustomPool(address creator, address tokenA, address tokenB, bytes data) external virtual returns (address customPool) -``` -**Selector**: `0x819dbd89` - -Create a custom pool with a plugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| creator | address | The address that initiated the pool creation | -| tokenA | address | The address of first token in pool | -| tokenB | address | The address of second token in pool | -| data | bytes | The data to be passed to beforeCreatePoolHook | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| customPool | address | The address of created plugin | - -### beforeCreatePoolHook - -```solidity -function beforeCreatePoolHook(address pool, address, address, address, address, bytes) external virtual returns (address) -``` -**Selector**: `0x1d0338d9` - -Deploys new plugin contract for pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of the new pool | -| | address | | -| | address | | -| | address | | -| | address | | -| | bytes | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | New plugin address | - -### afterCreatePoolHook - -```solidity -function afterCreatePoolHook(address, address, address) external view virtual -``` -**Selector**: `0x8d5ef8d1` - -Called after the pool is created - -| Name | Type | Description | -| ---- | ---- | ----------- | -| | address | | -| | address | | -| | address | | - diff --git a/docs/Contracts/Plugin/interfaces/IAlgebraBasePluginV1.md b/docs/Contracts/Plugin/interfaces/IAlgebraBasePluginV1.md deleted file mode 100644 index 67d613bcc..000000000 --- a/docs/Contracts/Plugin/interfaces/IAlgebraBasePluginV1.md +++ /dev/null @@ -1,25 +0,0 @@ - - -# IAlgebraBasePluginV1 - - -The interface for the AlgebraBasePluginV1 - -This contract combines the standard implementations of the volatility oracle and the dynamic fee manager - -*Developer note: This contract stores timepoints and calculates adaptive fee and statistical averages* - -**Inherits:** [IVolatilityOracle](plugins/IVolatilityOracle.md) [IDynamicFeeManager](plugins/IDynamicFeeManager.md) [IFarmingPlugin](plugins/IFarmingPlugin.md) - -## Functions -### initialize - -```solidity -function initialize() external -``` -**Selector**: `0x8129fc1c` - -Initialize the plugin externally - -*Developer note: This function allows to initialize the plugin if it was created after the pool was created* - diff --git a/docs/Contracts/Plugin/interfaces/IAlgebraVirtualPool.md b/docs/Contracts/Plugin/interfaces/IAlgebraVirtualPool.md deleted file mode 100644 index 3efe4ce0e..000000000 --- a/docs/Contracts/Plugin/interfaces/IAlgebraVirtualPool.md +++ /dev/null @@ -1,36 +0,0 @@ - - -# IAlgebraVirtualPool - - -The interface for the virtual pool - - - -*Developer note: Used to calculate active liquidity in farmings* - - -## Functions -### crossTo - -```solidity -function crossTo(int24 targetTick, bool zeroToOne) external returns (bool success) -``` -**Selector**: `0x34d33590` - - - -*Developer note: This function is called by the main pool if an initialized ticks are crossed by swap. -If any one of crossed ticks is also initialized in a virtual pool it should be crossed too* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| targetTick | int24 | The target tick up to which we need to cross all active ticks | -| zeroToOne | bool | Swap direction | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| success | bool | | - diff --git a/docs/Contracts/Plugin/interfaces/IBasePlugin.md b/docs/Contracts/Plugin/interfaces/IBasePlugin.md deleted file mode 100644 index dc4c8db92..000000000 --- a/docs/Contracts/Plugin/interfaces/IBasePlugin.md +++ /dev/null @@ -1,27 +0,0 @@ - - -# IBasePlugin - - -The interface for the BasePlugin - - - -**Inherits:** [IAlgebraPlugin](../../Core/interfaces/plugin/IAlgebraPlugin.md) - -## Functions -### collectPluginFee - -```solidity -function collectPluginFee(address token, uint256 amount, address recipient) external -``` -**Selector**: `0xe72c652d` - -Claim plugin fee - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token | address | The token address | -| amount | uint256 | Amount of tokens | -| recipient | address | Recipient address | - diff --git a/docs/Contracts/Plugin/interfaces/IBasePluginFactory.md b/docs/Contracts/Plugin/interfaces/IBasePluginFactory.md deleted file mode 100644 index ecab08ef1..000000000 --- a/docs/Contracts/Plugin/interfaces/IBasePluginFactory.md +++ /dev/null @@ -1,107 +0,0 @@ - - -# IBasePluginFactory - - -The interface for the BasePluginFactory - - - -**Inherits:** [IAlgebraPluginFactory](../../Core/interfaces/plugin/IAlgebraPluginFactory.md) - -## Functions -### entryPoint - -```solidity -function entryPoint() external view returns (address) -``` -**Selector**: `0xb0d691fe` - -Returns the address of AlgebraCustomPoolEntryPoint - -*Developer note: This is a main entry point for creating, managing plugins* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The AlgebraCustomPoolEntryPoint contract address | - -### createCustomPool - -```solidity -function createCustomPool(address creator, address tokenA, address tokenB, bytes data) external returns (address customPool) -``` -**Selector**: `0x819dbd89` - -Create a custom pool with a plugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| creator | address | The address that initiated the pool creation | -| tokenA | address | The address of first token in pool | -| tokenB | address | The address of second token in pool | -| data | bytes | The data to be passed to beforeCreatePoolHook | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| customPool | address | The address of created plugin | - -### setTickSpacing - -```solidity -function setTickSpacing(address pool, int24 newTickSpacing) external -``` -**Selector**: `0x4bf092cd` - -Sets tick spacing in a deployed custom pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of custom pool | -| newTickSpacing | int24 | The new tick spacing | - -### setPlugin - -```solidity -function setPlugin(address pool, address newPluginAddress) external -``` -**Selector**: `0xf9f4c09a` - -Sets plugin in a deployed custom pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of custom pool | -| newPluginAddress | address | The new plugin | - -### setPluginConfig - -```solidity -function setPluginConfig(address pool, uint8 newConfig) external -``` -**Selector**: `0x054bee3d` - -Sets plugin config in a deployed custom pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of custom pool | -| newConfig | uint8 | The new config | - -### setFee - -```solidity -function setFee(address pool, uint16 newFee) external -``` -**Selector**: `0x337f3a31` - -Sets fee in a deployed custom pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of custom pool | -| newFee | uint16 | The new fee | - diff --git a/docs/Contracts/Plugin/interfaces/IBasePluginV1Factory.md b/docs/Contracts/Plugin/interfaces/IBasePluginV1Factory.md deleted file mode 100644 index 95139f38b..000000000 --- a/docs/Contracts/Plugin/interfaces/IBasePluginV1Factory.md +++ /dev/null @@ -1,181 +0,0 @@ - - -# IBasePluginV1Factory - - -The interface for the BasePluginV1Factory - -This contract creates Algebra default plugins for Algebra liquidity pools - -**Inherits:** [IAlgebraPluginFactory](../../Core/interfaces/plugin/IAlgebraPluginFactory.md) - -## Events -### DefaultFeeConfiguration - -```solidity -event DefaultFeeConfiguration(struct AlgebraFeeConfiguration newConfig) -``` - -Emitted when the default fee configuration is changed - -*Developer note: See the AdaptiveFee library for more details* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newConfig | struct AlgebraFeeConfiguration | The structure with dynamic fee parameters | - -### FarmingAddress - -```solidity -event FarmingAddress(address newFarmingAddress) -``` - -Emitted when the farming address is changed - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newFarmingAddress | address | The farming address after the address was changed | - - -## Functions -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR - -```solidity -function ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR() external pure returns (bytes32) -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV1Factory* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes32 | | - -### algebraFactory - -```solidity -function algebraFactory() external view returns (address) -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The AlgebraFactory contract address | - -### defaultFeeConfiguration - -```solidity -function defaultFeeConfiguration() external view returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee) -``` -**Selector**: `0x4e09a96a` - -Current default dynamic fee configuration - -*Developer note: See the AdaptiveFee struct for more details about params. -This value is set by default in new plugins* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| alpha1 | uint16 | | -| alpha2 | uint16 | | -| beta1 | uint32 | | -| beta2 | uint32 | | -| gamma1 | uint16 | | -| gamma2 | uint16 | | -| baseFee | uint16 | | - -### farmingAddress - -```solidity -function farmingAddress() external view returns (address) -``` -**Selector**: `0x8a2ade58` - -Returns current farming address - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The farming contract address | - -### pluginByPool - -```solidity -function pluginByPool(address pool) external view returns (address) -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of AlgebraPool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of corresponding plugin | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setDefaultFeeConfiguration - -```solidity -function setDefaultFeeConfiguration(struct AlgebraFeeConfiguration newConfig) external -``` -**Selector**: `0xf718949a` - -Changes initial fee configuration for new pools - -*Developer note: changes coefficients for sigmoids: α / (1 + e^( (β-x) / γ)) -alpha1 + alpha2 + baseFee (max possible fee) must be <= type(uint16).max and gammas must be > 0* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newConfig | struct AlgebraFeeConfiguration | new default fee configuration. See the #AdaptiveFee.sol library for details | - -### setFarmingAddress - -```solidity -function setFarmingAddress(address newFarmingAddress) external -``` -**Selector**: `0xb001f618` - - - -*Developer note: updates farmings manager address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newFarmingAddress | address | The new tokenomics contract address | - diff --git a/docs/Contracts/Plugin/interfaces/IBasePluginV2Factory.md b/docs/Contracts/Plugin/interfaces/IBasePluginV2Factory.md deleted file mode 100644 index 0b9bc7943..000000000 --- a/docs/Contracts/Plugin/interfaces/IBasePluginV2Factory.md +++ /dev/null @@ -1,167 +0,0 @@ - - -# IBasePluginV2Factory - - -The interface for the BasePluginV2Factory - -This contract creates Algebra default plugins for Algebra liquidity pools - -**Inherits:** [IAlgebraPluginFactory](../../Core/interfaces/plugin/IAlgebraPluginFactory.md) - -## Events -### FarmingAddress - -```solidity -event FarmingAddress(address newFarmingAddress) -``` - -Emitted when the farming address is changed - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newFarmingAddress | address | The farming address after the address was changed | - -### DefaultBaseFee - -```solidity -event DefaultBaseFee(uint16 newDefaultBaseFee) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newDefaultBaseFee | uint16 | | - - -## Functions -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR - -```solidity -function ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR() external pure returns (bytes32) -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV2Factory* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes32 | | - -### algebraFactory - -```solidity -function algebraFactory() external view returns (address) -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The AlgebraFactory contract address | - -### farmingAddress - -```solidity -function farmingAddress() external view returns (address) -``` -**Selector**: `0x8a2ade58` - -Returns current farming address - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The farming contract address | - -### defaultBaseFee - -```solidity -function defaultBaseFee() external view returns (uint16) -``` -**Selector**: `0x675ec3d7` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint16 | | - -### pluginByPool - -```solidity -function pluginByPool(address pool) external view returns (address) -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of AlgebraPool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of corresponding plugin | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setFarmingAddress - -```solidity -function setFarmingAddress(address newFarmingAddress) external -``` -**Selector**: `0xb001f618` - - - -*Developer note: updates farmings manager address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newFarmingAddress | address | The new tokenomics contract address | - -### setDefaultBaseFee - -```solidity -function setDefaultBaseFee(uint16 newDefaultBaseFee) external -``` -**Selector**: `0x0bc614c4` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newDefaultBaseFee | uint16 | | - diff --git a/docs/Contracts/Plugin/interfaces/IHydrexBasePluginFactory.md b/docs/Contracts/Plugin/interfaces/IHydrexBasePluginFactory.md deleted file mode 100644 index 49f80eb65..000000000 --- a/docs/Contracts/Plugin/interfaces/IHydrexBasePluginFactory.md +++ /dev/null @@ -1,301 +0,0 @@ - - -# IHydrexBasePluginFactory - - -The interface for the HydrexBasePluginFactory - -This contract creates Algebra base plugins for Algebra liquidity pools - -**Inherits:** [IAlgebraPluginFactory](../../Core/interfaces/plugin/IAlgebraPluginFactory.md) - -## Events -### DefaultFeeConfiguration - -```solidity -event DefaultFeeConfiguration(struct AlgebraFeeConfiguration newConfig) -``` - -Emitted when the default fee configuration is changed - -*Developer note: See the AdaptiveFee library for more details* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newConfig | struct AlgebraFeeConfiguration | The structure with dynamic fee parameters | - -### DefaultBaseFee - -```solidity -event DefaultBaseFee(uint16 newDefaultBaseFee) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newDefaultBaseFee | uint16 | | - -### DynamicFeeStatus - -```solidity -event DynamicFeeStatus(bool isEnabled) -``` - -Emitted when the dynamic fee status is changed - -| Name | Type | Description | -| ---- | ---- | ----------- | -| isEnabled | bool | Dynamic fee new status | - -### SlidingFeeStatus - -```solidity -event SlidingFeeStatus(bool isEnabled) -``` - -Emitted when the sliding fee status is changed - -| Name | Type | Description | -| ---- | ---- | ----------- | -| isEnabled | bool | Sliding fee new status | - -### SecurityRegistry - -```solidity -event SecurityRegistry(address securityRegistry) -``` - -Emitted when the security registry address is changed - -| Name | Type | Description | -| ---- | ---- | ----------- | -| securityRegistry | address | The security registry address after the address was changed | - - -## Functions -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR - -```solidity -function ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR() external pure returns (bytes32) -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV1Factory* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes32 | | - -### algebraFactory - -```solidity -function algebraFactory() external view returns (address) -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The AlgebraFactory contract address | - -### defaultBaseFee - -```solidity -function defaultBaseFee() external view returns (uint16) -``` -**Selector**: `0x675ec3d7` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint16 | | - -### slidingFeeStatus - -```solidity -function slidingFeeStatus() external view returns (bool) -``` -**Selector**: `0xeb7bfd70` - -Returns the status of the sliding fee - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bool | The status of the sliding fee | - -### dynamicFeeStatus - -```solidity -function dynamicFeeStatus() external view returns (bool) -``` -**Selector**: `0xb7c53e79` - -Returns the status of the dynamic fee - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bool | The status of the dynamic fee | - -### securityRegistry - -```solidity -function securityRegistry() external view returns (address) -``` -**Selector**: `0x9b21f9ae` - -Returns current securityRegistry address - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The securityRegistry contract address | - -### defaultFeeConfiguration - -```solidity -function defaultFeeConfiguration() external view returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee) -``` -**Selector**: `0x4e09a96a` - -Current default dynamic fee configuration - -*Developer note: See the AdaptiveFee struct for more details about params. -This value is set by default in new plugins* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| alpha1 | uint16 | | -| alpha2 | uint16 | | -| beta1 | uint32 | | -| beta2 | uint32 | | -| gamma1 | uint16 | | -| gamma2 | uint16 | | -| baseFee | uint16 | | - -### pluginByPool - -```solidity -function pluginByPool(address pool) external view returns (address) -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of AlgebraPool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of corresponding plugin | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setDefaultFeeConfiguration - -```solidity -function setDefaultFeeConfiguration(struct AlgebraFeeConfiguration newConfig) external -``` -**Selector**: `0xf718949a` - -Changes initial fee configuration for new pools - -*Developer note: changes coefficients for sigmoids: α / (1 + e^( (β-x) / γ)) -alpha1 + alpha2 + baseFee (max possible fee) must be <= type(uint16).max and gammas must be > 0* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newConfig | struct AlgebraFeeConfiguration | new default fee configuration. See the #AdaptiveFee.sol library for details | - -### setDynamicFeeStatus - -```solidity -function setDynamicFeeStatus(bool status) external -``` -**Selector**: `0x141773b3` - -Changes dynamic fee status - -| Name | Type | Description | -| ---- | ---- | ----------- | -| status | bool | New status of dynamic fee | - -### setSlidingFeeStatus - -```solidity -function setSlidingFeeStatus(bool status) external -``` -**Selector**: `0x03801165` - -Changes sliding fee status - -| Name | Type | Description | -| ---- | ---- | ----------- | -| status | bool | New status of sliding fee | - -### setSecurityRegistry - -```solidity -function setSecurityRegistry(address newSecurityRegistry) external -``` -**Selector**: `0x64fae8a9` - - - -*Developer note: updates securoty registry address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newSecurityRegistry | address | The new security registry contract address | - -### setDefaultBaseFee - -```solidity -function setDefaultBaseFee(uint16 newDefaultBaseFee) external -``` -**Selector**: `0x0bc614c4` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newDefaultBaseFee | uint16 | | - diff --git a/docs/Contracts/Plugin/interfaces/ISecurityPluginFactory.md b/docs/Contracts/Plugin/interfaces/ISecurityPluginFactory.md deleted file mode 100644 index 270246fee..000000000 --- a/docs/Contracts/Plugin/interfaces/ISecurityPluginFactory.md +++ /dev/null @@ -1,127 +0,0 @@ - - -# ISecurityPluginFactory - - -The interface for the SecurityPluginFactory - - - -**Inherits:** [IAlgebraPluginFactory](../../Core/interfaces/plugin/IAlgebraPluginFactory.md) - -## Events -### SecurityRegistry - -```solidity -event SecurityRegistry(address securityRegistry) -``` - -Emitted when the security registry address is changed - -| Name | Type | Description | -| ---- | ---- | ----------- | -| securityRegistry | address | The security registry address after the address was changed | - - -## Functions -### ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR - -```solidity -function ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR() external pure returns (bytes32) -``` -**Selector**: `0xcddff269` - -The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - -*Developer note: allows to change settings of BasePluginV1Factory* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes32 | | - -### algebraFactory - -```solidity -function algebraFactory() external view returns (address) -``` -**Selector**: `0xa7b64b04` - -Returns the address of AlgebraFactory - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The AlgebraFactory contract address | - -### securityRegistry - -```solidity -function securityRegistry() external view returns (address) -``` -**Selector**: `0x9b21f9ae` - -Returns current securityRegistry address - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The securityRegistry contract address | - -### pluginByPool - -```solidity -function pluginByPool(address pool) external view returns (address) -``` -**Selector**: `0xcdef16f6` - -Returns address of plugin created for given AlgebraPool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of AlgebraPool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of corresponding plugin | - -### createPluginForExistingPool - -```solidity -function createPluginForExistingPool(address token0, address token1) external returns (address) -``` -**Selector**: `0x27733026` - -Create plugin for already existing pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| token0 | address | The address of first token in pool | -| token1 | address | The address of second token in pool | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address of created plugin | - -### setSecurityRegistry - -```solidity -function setSecurityRegistry(address newSecurityRegistry) external -``` -**Selector**: `0x64fae8a9` - - - -*Developer note: updates securoty registry address on the factory* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newSecurityRegistry | address | The new security registry contract address | - diff --git a/docs/Contracts/Plugin/interfaces/plugins/IAlmPlugin.md b/docs/Contracts/Plugin/interfaces/plugins/IAlmPlugin.md deleted file mode 100644 index 1d53132a1..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/IAlmPlugin.md +++ /dev/null @@ -1,110 +0,0 @@ - - -# IAlmPlugin - - - - - - - -## Functions -### initializeALM - -```solidity -function initializeALM(address _rebalanceManager, uint32 _slowTwapPeriod, uint32 _fastTwapPeriod) external -``` -**Selector**: `0xd49dda85` - -Initializing ALM plugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _rebalanceManager | address | address of rebalance manager | -| _slowTwapPeriod | uint32 | period in seconds to get slow TWAP | -| _fastTwapPeriod | uint32 | period in seconds to get fast TWAP | - -### setSlowTwapPeriod - -```solidity -function setSlowTwapPeriod(uint32 _slowTwapPeriod) external -``` -**Selector**: `0x48b2acdd` - -Set slow TWAP period - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _slowTwapPeriod | uint32 | period in seconds to get slow TWAP | - -### setFastTwapPeriod - -```solidity -function setFastTwapPeriod(uint32 _fastTwapPeriod) external -``` -**Selector**: `0x08095141` - -Set slow TWAP period - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _fastTwapPeriod | uint32 | period in seconds to get fast TWAP | - -### setRebalanceManager - -```solidity -function setRebalanceManager(address _rebalanceManager) external -``` -**Selector**: `0x918a1ab0` - -Set rebalance manager - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _rebalanceManager | address | address of rebalance manager | - -### rebalanceManager - -```solidity -function rebalanceManager() external view returns (address) -``` -**Selector**: `0x6fb5bad1` - -Returns address of rebalance manager - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | Address of rebalance manager | - -### slowTwapPeriod - -```solidity -function slowTwapPeriod() external view returns (uint32) -``` -**Selector**: `0x841c6a37` - -Returns time interval in seconds of slow TWAP period - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint32 | Time interval in seconds of slow TWAP period | - -### fastTwapPeriod - -```solidity -function fastTwapPeriod() external view returns (uint32) -``` -**Selector**: `0xad1c3743` - -Returns time interval in seconds of fast TWAP period - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint32 | Time interval in seconds of fast TWAP period | - diff --git a/docs/Contracts/Plugin/interfaces/plugins/IDynamicFeeManager.md b/docs/Contracts/Plugin/interfaces/plugins/IDynamicFeeManager.md deleted file mode 100644 index 2a2d84249..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/IDynamicFeeManager.md +++ /dev/null @@ -1,66 +0,0 @@ - - -# IDynamicFeeManager - - -The interface for the Algebra dynamic fee manager - - - -*Developer note: This contract calculates adaptive fee* - -**Inherits:** [IAlgebraDynamicFeePlugin](../../../Core/interfaces/plugin/IAlgebraDynamicFeePlugin.md) - -## Events -### FeeConfiguration - -```solidity -event FeeConfiguration(struct AlgebraFeeConfiguration feeConfig) -``` - -Emitted when the fee configuration is changed - -*Developer note: See the AdaptiveFee struct for more details* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| feeConfig | struct AlgebraFeeConfiguration | The structure with dynamic fee parameters | - - -## Functions -### feeConfig - -```solidity -function feeConfig() external view returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee) -``` -**Selector**: `0x1e5eb1d0` - -Current dynamic fee configuration - -*Developer note: See the AdaptiveFee struct for more details* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| alpha1 | uint16 | | -| alpha2 | uint16 | | -| beta1 | uint32 | | -| beta2 | uint32 | | -| gamma1 | uint16 | | -| gamma2 | uint16 | | -| baseFee | uint16 | | - -### changeFeeConfiguration - -```solidity -function changeFeeConfiguration(struct AlgebraFeeConfiguration feeConfig) external -``` -**Selector**: `0x1d39215e` - -Changes fee configuration for the pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| feeConfig | struct AlgebraFeeConfiguration | | - diff --git a/docs/Contracts/Plugin/interfaces/plugins/IFarmingPlugin.md b/docs/Contracts/Plugin/interfaces/plugins/IFarmingPlugin.md deleted file mode 100644 index 2335a78eb..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/IFarmingPlugin.md +++ /dev/null @@ -1,97 +0,0 @@ - - -# IFarmingPlugin - - -The interface for the Algebra farming plugin - - - -*Developer note: This contract used for virtual pools in farms* - - -## Events -### Incentive - -```solidity -event Incentive(address newIncentive) -``` - -Emitted when new activeIncentive is set - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newIncentive | address | The address of the new incentive | - - -## Functions -### setIncentive - -```solidity -function setIncentive(address newIncentive) external -``` -**Selector**: `0x7c1fe0c8` - -Connects or disconnects an incentive. - -*Developer note: Only farming can connect incentives. -The one who connected it and the current farming has the right to disconnect the incentive.* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newIncentive | address | The address associated with the incentive or zero address | - -### isIncentiveConnected - -```solidity -function isIncentiveConnected(address targetIncentive) external view returns (bool) -``` -**Selector**: `0xe63015f0` - -Checks if the incentive is connected to pool - -*Developer note: Returns false if the plugin has a different incentive set, the plugin is not connected to the pool, -or the plugin configuration is incorrect.* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| targetIncentive | address | The address of the incentive to be checked | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bool | Indicates whether the target incentive is active | - -### incentive - -```solidity -function incentive() external view returns (address) -``` -**Selector**: `0x1d4632ac` - -Returns the address of active incentive - -*Developer note: if there is no active incentive at the moment, incentiveAddress would be equal to address(0)* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | The address associated with the current active incentive | - -### getPool - -```solidity -function getPool() external view returns (address) -``` -**Selector**: `0x026b1d5f` - -Returns the address of the pool the plugin is created for - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | address of the pool | - diff --git a/docs/Contracts/Plugin/interfaces/plugins/IRebalanceManager.md b/docs/Contracts/Plugin/interfaces/plugins/IRebalanceManager.md deleted file mode 100644 index e1f08ef30..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/IRebalanceManager.md +++ /dev/null @@ -1,170 +0,0 @@ - - -# IRebalanceManager - - - - - - - -## Events -### SetPriceChangeThreshold - -```solidity -event SetPriceChangeThreshold(uint16 priceChangeThreshold) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| priceChangeThreshold | uint16 | | - -### SetPercentages - -```solidity -event SetPercentages(uint16 baseLowPct, uint16 baseHighPct, uint16 limitReservePct) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| baseLowPct | uint16 | | -| baseHighPct | uint16 | | -| limitReservePct | uint16 | | - -### SetTriggers - -```solidity -event SetTriggers(uint16 simulate, uint16 normalThreshold, uint16 underInventoryThreshold, uint16 overInventoryThreshold) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| simulate | uint16 | | -| normalThreshold | uint16 | | -| underInventoryThreshold | uint16 | | -| overInventoryThreshold | uint16 | | - -### SetDtrDelta - -```solidity -event SetDtrDelta(uint16 dtrDelta) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| dtrDelta | uint16 | | - -### SetHighVolatility - -```solidity -event SetHighVolatility(uint16 highVolatility) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| highVolatility | uint16 | | - -### SetSomeVolatility - -```solidity -event SetSomeVolatility(uint16 someVolatility) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| someVolatility | uint16 | | - -### SetExtremeVolatility - -```solidity -event SetExtremeVolatility(uint16 extremeVolatility) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| extremeVolatility | uint16 | | - -### SetDepositTokenUnusedThreshold - -```solidity -event SetDepositTokenUnusedThreshold(uint16 depositTokenUnusedThreshold) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| depositTokenUnusedThreshold | uint16 | | - -### SetMinTimeBetweenRebalances - -```solidity -event SetMinTimeBetweenRebalances(uint32 minTimeBetweenRebalances) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| minTimeBetweenRebalances | uint32 | | - -### SetVault - -```solidity -event SetVault(address vault) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| vault | address | | - -### Paused - -```solidity -event Paused() -``` - - - -### Unpaused - -```solidity -event Unpaused() -``` - - - - -## Functions -### obtainTWAPAndRebalance - -```solidity -function obtainTWAPAndRebalance(int24 currentTick, int24 slowTwapTick, int24 fastTwapTick, uint32 lastBlockTimestamp) external -``` -**Selector**: `0x7e7b25f1` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| currentTick | int24 | | -| slowTwapTick | int24 | | -| fastTwapTick | int24 | | -| lastBlockTimestamp | uint32 | | - diff --git a/docs/Contracts/Plugin/interfaces/plugins/ISecurityPlugin.md b/docs/Contracts/Plugin/interfaces/plugins/ISecurityPlugin.md deleted file mode 100644 index 9b57d66b4..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/ISecurityPlugin.md +++ /dev/null @@ -1,73 +0,0 @@ - - -# ISecurityPlugin - - - - - - - -## Events -### SecurityRegistry - -```solidity -event SecurityRegistry(address registry) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| registry | address | | - - -## Functions -### setSecurityRegistry - -```solidity -function setSecurityRegistry(address registry) external -``` -**Selector**: `0x64fae8a9` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| registry | address | | - -### getSecurityRegistry - -```solidity -function getSecurityRegistry() external view returns (address) -``` -**Selector**: `0x20501a91` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | | - - -## Errors -## PoolDisabled - -```solidity -error PoolDisabled() -``` -**Selector**: `0x19d5b294` - - - -## BurnOnly - -```solidity -error BurnOnly() -``` -**Selector**: `0x5261f42a` - - - diff --git a/docs/Contracts/Plugin/interfaces/plugins/ISecurityRegistry.md b/docs/Contracts/Plugin/interfaces/plugins/ISecurityRegistry.md deleted file mode 100644 index d7f972c49..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/ISecurityRegistry.md +++ /dev/null @@ -1,155 +0,0 @@ - - -# ISecurityRegistry - - - - - - - -## Events -### GlobalStatus - -```solidity -event GlobalStatus(enum ISecurityRegistry.Status status) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| status | enum ISecurityRegistry.Status | | - -### PoolStatus - -```solidity -event PoolStatus(address pool, enum ISecurityRegistry.Status status) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | -| status | enum ISecurityRegistry.Status | | - - -## Functions -### setGlobalStatus - -```solidity -function setGlobalStatus(enum ISecurityRegistry.Status newStatus) external -``` -**Selector**: `0x24b62cd0` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newStatus | enum ISecurityRegistry.Status | | - -### getPoolStatus - -```solidity -function getPoolStatus(address pool) external returns (enum ISecurityRegistry.Status) -``` -**Selector**: `0x15d9d2f9` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | enum ISecurityRegistry.Status | | - -### setPoolsStatus - -```solidity -function setPoolsStatus(address[] pools, enum ISecurityRegistry.Status[] newStatuses) external -``` -**Selector**: `0x6547cd53` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pools | address[] | | -| newStatuses | enum ISecurityRegistry.Status[] | | - -### algebraFactory - -```solidity -function algebraFactory() external view returns (address) -``` -**Selector**: `0xa7b64b04` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | | - -### GUARD - -```solidity -function GUARD() external pure returns (bytes32) -``` -**Selector**: `0xfe3348f9` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes32 | | - -### globalStatus - -```solidity -function globalStatus() external view returns (enum ISecurityRegistry.Status) -``` -**Selector**: `0xb73f7951` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | enum ISecurityRegistry.Status | | - -### isPoolStatusOverrided - -```solidity -function isPoolStatusOverrided() external view returns (bool) -``` -**Selector**: `0x853b8c5c` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bool | | - - -## Errors -## OnlyOwner - -```solidity -error OnlyOwner() -``` -**Selector**: `0x5fc483c5` - - - diff --git a/docs/Contracts/Plugin/interfaces/plugins/ISlidingFeePlugin.md b/docs/Contracts/Plugin/interfaces/plugins/ISlidingFeePlugin.md deleted file mode 100644 index 400dc2e87..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/ISlidingFeePlugin.md +++ /dev/null @@ -1,63 +0,0 @@ - - -# ISlidingFeePlugin - - - - - - - -## Events -### PriceChangeFactor - -```solidity -event PriceChangeFactor(uint256 priceChangeFactor) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| priceChangeFactor | uint256 | | - -### BaseFee - -```solidity -event BaseFee(uint16 baseFee) -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| baseFee | uint16 | | - - -## Functions -### setBaseFee - -```solidity -function setBaseFee(uint16 newBaseFee) external -``` -**Selector**: `0x3b586c7f` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newBaseFee | uint16 | | - -### setPriceChangeFactor - -```solidity -function setPriceChangeFactor(uint16 newPriceChangeFactor) external -``` -**Selector**: `0xa37a8456` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newPriceChangeFactor | uint16 | | - diff --git a/docs/Contracts/Plugin/interfaces/plugins/IVolatilityOracle.md b/docs/Contracts/Plugin/interfaces/plugins/IVolatilityOracle.md deleted file mode 100644 index 84c995508..000000000 --- a/docs/Contracts/Plugin/interfaces/plugins/IVolatilityOracle.md +++ /dev/null @@ -1,161 +0,0 @@ - - -# IVolatilityOracle - - -The interface for the Algebra volatility oracle - - - -*Developer note: This contract stores timepoints and calculates statistical averages* - - -## Functions -### timepoints - -```solidity -function timepoints(uint256 index) external view returns (bool initialized, uint32 blockTimestamp, int56 tickCumulative, uint88 volatilityCumulative, int24 tick, int24 averageTick, uint16 windowStartIndex) -``` -**Selector**: `0x74eceae6` - -Returns data belonging to a certain timepoint - -*Developer note: There is more convenient function to fetch a timepoint: getTimepoints(). Which requires not an index but seconds* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| index | uint256 | The index of timepoint in the array | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| initialized | bool | Whether the timepoint has been initialized and the values are safe to use | -| blockTimestamp | uint32 | The timestamp of the timepoint | -| tickCumulative | int56 | The tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp | -| volatilityCumulative | uint88 | Cumulative standard deviation for the life of the pool as of the timepoint timestamp | -| tick | int24 | The tick at blockTimestamp | -| averageTick | int24 | Time-weighted average tick | -| windowStartIndex | uint16 | Index of closest timepoint >= WINDOW seconds ago | - -### timepointIndex - -```solidity -function timepointIndex() external view returns (uint16) -``` -**Selector**: `0x0786feb6` - -Returns the index of the last timepoint that was written. - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint16 | index of the last timepoint written | - -### initialize - -```solidity -function initialize() external -``` -**Selector**: `0x8129fc1c` - -Initialize the plugin externally - -*Developer note: This function allows to initialize the plugin if it was created after the pool was created* - -### lastTimepointTimestamp - -```solidity -function lastTimepointTimestamp() external view returns (uint32) -``` -**Selector**: `0xf5985d35` - -Returns the timestamp of the last timepoint that was written. - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint32 | timestamp of the last timepoint | - -### isInitialized - -```solidity -function isInitialized() external view returns (bool) -``` -**Selector**: `0x392e53cd` - -Returns information about whether oracle is initialized - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bool | true if oracle is initialized, otherwise false | - -### getSingleTimepoint - -```solidity -function getSingleTimepoint(uint32 secondsAgo) external view returns (int56 tickCumulative, uint88 volatilityCumulative) -``` -**Selector**: `0x88f2e862` - - - -*Developer note: Reverts if a timepoint at or before the desired timepoint timestamp does not exist. -0 may be passed as `secondsAgo' to return the current cumulative values. -If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values -at exactly the timestamp between the two timepoints. -`volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| secondsAgo | uint32 | The amount of time to look back, in seconds, at which point to return a timepoint | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| tickCumulative | int56 | The cumulative tick since the pool was first initialized, as of `secondsAgo` | -| volatilityCumulative | uint88 | The cumulative volatility value since the pool was first initialized, as of `secondsAgo` | - -### getTimepoints - -```solidity -function getTimepoints(uint32[] secondsAgos) external view returns (int56[] tickCumulatives, uint88[] volatilityCumulatives) -``` -**Selector**: `0x9d3a5241` - -Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` - -*Developer note: Reverts if `secondsAgos` > oldest timepoint -`volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| secondsAgos | uint32[] | Each amount of time to look back, in seconds, at which point to return a timepoint | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| tickCumulatives | int56[] | The cumulative tick since the pool was first initialized, as of each `secondsAgo` | -| volatilityCumulatives | uint88[] | The cumulative volatility values since the pool was first initialized, as of each `secondsAgo` | - -### prepayTimepointsStorageSlots - -```solidity -function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external -``` -**Selector**: `0xda705235` - -Fills uninitialized timepoints with nonzero value - -*Developer note: Can be used to reduce the gas cost of future swaps* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| startIndex | uint16 | The start index, must be not initialized | -| amount | uint16 | of slots to fill, startIndex + amount must be <= type(uint16).max | - diff --git a/docs/Contracts/Plugin/lens/AlgebraOracleV1TWAP.md b/docs/Contracts/Plugin/lens/AlgebraOracleV1TWAP.md deleted file mode 100644 index c3d79e9bf..000000000 --- a/docs/Contracts/Plugin/lens/AlgebraOracleV1TWAP.md +++ /dev/null @@ -1,183 +0,0 @@ - - -# AlgebraOracleV1TWAP - - -Algebra Integral 1.2.2 base plugin V1 oracle frontend - -Provides data from oracle corresponding pool - -*Developer note: These functions are not very gas efficient and it is better not to use them on-chain* - -**Inherits:** [IAlgebraOracleV1TWAP](IAlgebraOracleV1TWAP.md) - -## Public variables -### pluginFactory -```solidity -address immutable pluginFactory -``` -**Selector**: `0xe2a1bd59` - -The address of the factory of plugins that are used as oracles by this contract - - - -## Functions -### constructor - -```solidity -constructor(address _pluginFactory) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _pluginFactory | address | | - -### getQuoteAtTick - -```solidity -function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) external pure returns (uint256 quoteAmount) -``` -**Selector**: `0x43c57a27` - -Given a tick and a token amount, calculates the amount of token received in exchange - -*Developer note: Should not be used as quote for swap* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| tick | int24 | Tick value used to calculate the quote | -| baseAmount | uint128 | Amount of token to be converted | -| baseToken | address | Address of an ERC20 token contract used as the baseAmount denomination | -| quoteToken | address | Address of an ERC20 token contract used as the quoteAmount denomination | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| quoteAmount | uint256 | Amount of quoteToken received for baseAmount of baseToken | - -### getAverageTick - -```solidity -function getAverageTick(address pool, uint32 period) external view returns (int24 timeWeightedAverageTick, bool isConnected) -``` -**Selector**: `0x57f32330` - -Fetches time-weighted average tick using Algebra VolatilityOracle - -*Developer note: Oracle may stop receiving data from the pool (be disconnected). For that reason it is important -not to rely on the absolute accuracy and availability at any time of this oracle. -It is recommended to check the latest available timestamp using the `latestTimestamp` method and don't use the data if the last entry is too old* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of Algebra Integral pool | -| period | uint32 | Number of seconds in the past to start calculating time-weighted average | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| timeWeightedAverageTick | int24 | The time-weighted average tick from (block.timestamp - period) to block.timestamp | -| isConnected | bool | Is oracle currently connected to the pool. If disconnected data can be obsolete | - -### latestTimestamp - -```solidity -function latestTimestamp(address pool) external view returns (uint32) -``` -**Selector**: `0x59dd1ce6` - -Returns the last timestamp written in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint32 | | - -### oldestTimestamp - -```solidity -function oldestTimestamp(address pool) external view returns (uint32 _oldestTimestamp) -``` -**Selector**: `0x42c7d5bf` - -Returns the oldest timestamp available in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _oldestTimestamp | uint32 | | - -### latestIndex - -```solidity -function latestIndex(address pool) external view returns (uint16) -``` -**Selector**: `0x057e9b96` - -Returns the index of last record written in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint16 | | - -### isOracleConnected - -```solidity -function isOracleConnected(address pool) external view returns (bool connected) -``` -**Selector**: `0x9905d9a5` - -Whether or not the oracle is connected to the liquidity pool - -*Developer note: Oracle should not be used if disconnected from pool* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| connected | bool | | - -### oldestIndex - -```solidity -function oldestIndex(address pool) external view returns (uint16 _oldestIndex) -``` -**Selector**: `0x4a2b7c70` - -Returns the index of oldest record available in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _oldestIndex | uint16 | | - diff --git a/docs/Contracts/Plugin/lens/IAlgebraOracleV1TWAP.md b/docs/Contracts/Plugin/lens/IAlgebraOracleV1TWAP.md deleted file mode 100644 index 9db690471..000000000 --- a/docs/Contracts/Plugin/lens/IAlgebraOracleV1TWAP.md +++ /dev/null @@ -1,174 +0,0 @@ - - -# IAlgebraOracleV1TWAP - - -Algebra base plugin V1 oracle frontend - -Provides data from oracle corresponding pool - -*Developer note: These functions are not very gas efficient and it is better not to use them on-chain* - - -## Functions -### pluginFactory - -```solidity -function pluginFactory() external view returns (address) -``` -**Selector**: `0xe2a1bd59` - -The address of the factory of plugins that are used as oracles by this contract - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | | - -### getQuoteAtTick - -```solidity -function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) external pure returns (uint256 quoteAmount) -``` -**Selector**: `0x43c57a27` - -Given a tick and a token amount, calculates the amount of token received in exchange - -*Developer note: Should not be used as quote for swap* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| tick | int24 | Tick value used to calculate the quote | -| baseAmount | uint128 | Amount of token to be converted | -| baseToken | address | Address of an ERC20 token contract used as the baseAmount denomination | -| quoteToken | address | Address of an ERC20 token contract used as the quoteAmount denomination | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| quoteAmount | uint256 | Amount of quoteToken received for baseAmount of baseToken | - -### getAverageTick - -```solidity -function getAverageTick(address pool, uint32 period) external view returns (int24 timeWeightedAverageTick, bool isConnected) -``` -**Selector**: `0x57f32330` - -Fetches time-weighted average tick using Algebra VolatilityOracle - -*Developer note: Oracle may stop receiving data from the pool (be disconnected). For that reason it is important -not to rely on the absolute accuracy and availability at any time of this oracle. -It is recommended to check the latest available timestamp using the `latestTimestamp` method and don't use the data if the last entry is too old* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | The address of Algebra Integral pool | -| period | uint32 | Number of seconds in the past to start calculating time-weighted average | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| timeWeightedAverageTick | int24 | The time-weighted average tick from (block.timestamp - period) to block.timestamp | -| isConnected | bool | Is oracle currently connected to the pool. If disconnected data can be obsolete | - -### latestTimestamp - -```solidity -function latestTimestamp(address pool) external view returns (uint32) -``` -**Selector**: `0x59dd1ce6` - -Returns the last timestamp written in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint32 | | - -### oldestTimestamp - -```solidity -function oldestTimestamp(address pool) external view returns (uint32 _oldestTimestamp) -``` -**Selector**: `0x42c7d5bf` - -Returns the oldest timestamp available in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _oldestTimestamp | uint32 | | - -### latestIndex - -```solidity -function latestIndex(address pool) external view returns (uint16) -``` -**Selector**: `0x057e9b96` - -Returns the index of last record written in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint16 | | - -### oldestIndex - -```solidity -function oldestIndex(address pool) external view returns (uint16) -``` -**Selector**: `0x4a2b7c70` - -Returns the index of oldest record available in the oracle - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | uint16 | | - -### isOracleConnected - -```solidity -function isOracleConnected(address pool) external view returns (bool connected) -``` -**Selector**: `0x9905d9a5` - -Whether or not the oracle is connected to the liquidity pool - -*Developer note: Oracle should not be used if disconnected from pool* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| connected | bool | | - diff --git a/docs/Contracts/Plugin/libraries/VolatilityOracle.md b/docs/Contracts/Plugin/libraries/VolatilityOracle.md deleted file mode 100644 index 8a925859e..000000000 --- a/docs/Contracts/Plugin/libraries/VolatilityOracle.md +++ /dev/null @@ -1,21 +0,0 @@ - - -## Errors -## targetIsTooOld - -```solidity -error targetIsTooOld() -``` -**Selector**: `0x0bc60f10` - -`target` timestamp is older than oldest timepoint - -## volatilityOracleAlreadyInitialized - -```solidity -error volatilityOracleAlreadyInitialized() -``` -**Selector**: `0x11562346` - -oracle is initialized already - diff --git a/docs/Contracts/Plugin/plugins/AlmPlugin.md b/docs/Contracts/Plugin/plugins/AlmPlugin.md deleted file mode 100644 index 844906f25..000000000 --- a/docs/Contracts/Plugin/plugins/AlmPlugin.md +++ /dev/null @@ -1,95 +0,0 @@ - - -# AlmPlugin - - - - - - -**Inherits:** AlgebraBasePlugin [IAlmPlugin](../interfaces/plugins/IAlmPlugin.md) - -## Public variables -### rebalanceManager -```solidity -address rebalanceManager -``` -**Selector**: `0x6fb5bad1` - -@inheritdoc IAlmPlugin - - -### slowTwapPeriod -```solidity -uint32 slowTwapPeriod -``` -**Selector**: `0x841c6a37` - -@inheritdoc IAlmPlugin - - -### fastTwapPeriod -```solidity -uint32 fastTwapPeriod -``` -**Selector**: `0xad1c3743` - -@inheritdoc IAlmPlugin - - - -## Functions -### initializeALM - -```solidity -function initializeALM(address _rebalanceManager, uint32 _slowTwapPeriod, uint32 _fastTwapPeriod) external -``` -**Selector**: `0xd49dda85` - -@inheritdoc IAlmPlugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _rebalanceManager | address | | -| _slowTwapPeriod | uint32 | | -| _fastTwapPeriod | uint32 | | - -### setSlowTwapPeriod - -```solidity -function setSlowTwapPeriod(uint32 _slowTwapPeriod) external -``` -**Selector**: `0x48b2acdd` - -@inheritdoc IAlmPlugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _slowTwapPeriod | uint32 | | - -### setFastTwapPeriod - -```solidity -function setFastTwapPeriod(uint32 _fastTwapPeriod) external -``` -**Selector**: `0x08095141` - -@inheritdoc IAlmPlugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _fastTwapPeriod | uint32 | | - -### setRebalanceManager - -```solidity -function setRebalanceManager(address _rebalanceManager) external -``` -**Selector**: `0x918a1ab0` - -@inheritdoc IAlmPlugin - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _rebalanceManager | address | | - diff --git a/docs/Contracts/Plugin/plugins/DynamicFeePlugin.md b/docs/Contracts/Plugin/plugins/DynamicFeePlugin.md deleted file mode 100644 index fbe791f7d..000000000 --- a/docs/Contracts/Plugin/plugins/DynamicFeePlugin.md +++ /dev/null @@ -1,48 +0,0 @@ - - -# DynamicFeePlugin - - -Algebra Integral 1.2.2 default plugin - -This contract stores timepoints and calculates adaptive fee and statistical averages - -**Inherits:** AlgebraBasePlugin [IDynamicFeeManager](../interfaces/plugins/IDynamicFeeManager.md) - -## Functions -### feeConfig - -```solidity -function feeConfig() external view returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee) -``` -**Selector**: `0x1e5eb1d0` - -Current dynamic fee configuration - -*Developer note: See the AdaptiveFee struct for more details* - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| alpha1 | uint16 | | -| alpha2 | uint16 | | -| beta1 | uint32 | | -| beta2 | uint32 | | -| gamma1 | uint16 | | -| gamma2 | uint16 | | -| baseFee | uint16 | | - -### changeFeeConfiguration - -```solidity -function changeFeeConfiguration(struct AlgebraFeeConfiguration _config) external -``` -**Selector**: `0x1d39215e` - -Changes fee configuration for the pool - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _config | struct AlgebraFeeConfiguration | | - diff --git a/docs/Contracts/Plugin/plugins/FarmingProxyPlugin.md b/docs/Contracts/Plugin/plugins/FarmingProxyPlugin.md deleted file mode 100644 index a5314dfba..000000000 --- a/docs/Contracts/Plugin/plugins/FarmingProxyPlugin.md +++ /dev/null @@ -1,77 +0,0 @@ - - -# FarmingProxyPlugin - - -Algebra Integral 1.2.2 default plugin - -This contract stores timepoints and calculates adaptive fee and statistical averages - -**Inherits:** AlgebraBasePlugin [IFarmingPlugin](../interfaces/plugins/IFarmingPlugin.md) - -## Public variables -### incentive -```solidity -address incentive -``` -**Selector**: `0x1d4632ac` - -Returns the address of active incentive - -*Developer note: if there is no active incentive at the moment, incentiveAddress would be equal to address(0)* - - -## Functions -### setIncentive - -```solidity -function setIncentive(address newIncentive) external -``` -**Selector**: `0x7c1fe0c8` - -Connects or disconnects an incentive. - -*Developer note: Only farming can connect incentives. -The one who connected it and the current farming has the right to disconnect the incentive.* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newIncentive | address | The address associated with the incentive or zero address | - -### isIncentiveConnected - -```solidity -function isIncentiveConnected(address targetIncentive) external view returns (bool) -``` -**Selector**: `0xe63015f0` - -Checks if the incentive is connected to pool - -*Developer note: Returns false if the plugin has a different incentive set, the plugin is not connected to the pool, -or the plugin configuration is incorrect.* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| targetIncentive | address | The address of the incentive to be checked | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bool | Indicates whether the target incentive is active | - -### getPool - -```solidity -function getPool() external view returns (address) -``` -**Selector**: `0x026b1d5f` - -Returns the address of the pool the plugin is created for - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | address of the pool | - diff --git a/docs/Contracts/Plugin/plugins/SecurityPlugin.md b/docs/Contracts/Plugin/plugins/SecurityPlugin.md deleted file mode 100644 index 5389b4bf4..000000000 --- a/docs/Contracts/Plugin/plugins/SecurityPlugin.md +++ /dev/null @@ -1,40 +0,0 @@ - - -# SecurityPlugin - - -Algebra Integral 1.2 security plugin - - - -**Inherits:** AlgebraBasePlugin [ISecurityPlugin](../interfaces/plugins/ISecurityPlugin.md) - -## Functions -### setSecurityRegistry - -```solidity -function setSecurityRegistry(address _securityRegistry) external -``` -**Selector**: `0x64fae8a9` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _securityRegistry | address | | - -### getSecurityRegistry - -```solidity -function getSecurityRegistry() external view returns (address) -``` -**Selector**: `0x20501a91` - - - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | address | | - diff --git a/docs/Contracts/Plugin/plugins/SecurityRegistry.md b/docs/Contracts/Plugin/plugins/SecurityRegistry.md deleted file mode 100644 index edcb10929..000000000 --- a/docs/Contracts/Plugin/plugins/SecurityRegistry.md +++ /dev/null @@ -1,117 +0,0 @@ - - -# SecurityRegistry - - - - - - -**Inherits:** [ISecurityRegistry](../interfaces/plugins/ISecurityRegistry.md) - -## Public variables -### algebraFactory -```solidity -address immutable algebraFactory -``` -**Selector**: `0xa7b64b04` - - - - -### GUARD -```solidity -bytes32 constant GUARD = 0x25bca7788d8c23352e368ccd4774eb5b5fc3d40422de2c14e98631ab71f33415 -``` -**Selector**: `0xfe3348f9` - - - - -### globalStatus -```solidity -enum ISecurityRegistry.Status globalStatus -``` -**Selector**: `0xb73f7951` - - - - -### isPoolStatusOverrided -```solidity -bool isPoolStatusOverrided -``` -**Selector**: `0x853b8c5c` - - - - -### poolStatus -```solidity -mapping(address => enum ISecurityRegistry.Status) poolStatus -``` -**Selector**: `0x3c38ccbb` - - - - - -## Functions -### constructor - -```solidity -constructor(address _algebraFactory) public -``` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| _algebraFactory | address | | - -### setPoolsStatus - -```solidity -function setPoolsStatus(address[] pools, enum ISecurityRegistry.Status[] newStatuses) external -``` -**Selector**: `0x6547cd53` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pools | address[] | | -| newStatuses | enum ISecurityRegistry.Status[] | | - -### setGlobalStatus - -```solidity -function setGlobalStatus(enum ISecurityRegistry.Status newStatus) external -``` -**Selector**: `0x24b62cd0` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newStatus | enum ISecurityRegistry.Status | | - -### getPoolStatus - -```solidity -function getPoolStatus(address pool) external view returns (enum ISecurityRegistry.Status) -``` -**Selector**: `0x15d9d2f9` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| pool | address | | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | enum ISecurityRegistry.Status | | - diff --git a/docs/Contracts/Plugin/plugins/SlidingFeePlugin.md b/docs/Contracts/Plugin/plugins/SlidingFeePlugin.md deleted file mode 100644 index b0cd05dca..000000000 --- a/docs/Contracts/Plugin/plugins/SlidingFeePlugin.md +++ /dev/null @@ -1,80 +0,0 @@ - - -# SlidingFeePlugin - - - - - - -**Inherits:** AlgebraBasePlugin [ISlidingFeePlugin](../interfaces/plugins/ISlidingFeePlugin.md) - -## Structs -### FeeFactors - - - -```solidity -struct FeeFactors { - uint128 zeroToOneFeeFactor; - uint128 oneToZeroFeeFactor; -} -``` - - -## Public variables -### s_feeFactors -```solidity -struct SlidingFeePlugin.FeeFactors s_feeFactors -``` -**Selector**: `0x58e31bfd` - - - - -### s_priceChangeFactor -```solidity -uint16 s_priceChangeFactor -``` -**Selector**: `0x7b3de5c6` - - - - -### s_baseFee -```solidity -uint16 s_baseFee -``` -**Selector**: `0x08cd1975` - - - - - -## Functions -### setPriceChangeFactor - -```solidity -function setPriceChangeFactor(uint16 newPriceChangeFactor) external -``` -**Selector**: `0xa37a8456` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newPriceChangeFactor | uint16 | | - -### setBaseFee - -```solidity -function setBaseFee(uint16 newBaseFee) external -``` -**Selector**: `0x3b586c7f` - - - -| Name | Type | Description | -| ---- | ---- | ----------- | -| newBaseFee | uint16 | | - diff --git a/docs/Contracts/Plugin/plugins/VolatilityOraclePlugin.md b/docs/Contracts/Plugin/plugins/VolatilityOraclePlugin.md deleted file mode 100644 index e784f9dae..000000000 --- a/docs/Contracts/Plugin/plugins/VolatilityOraclePlugin.md +++ /dev/null @@ -1,127 +0,0 @@ - - -# VolatilityOraclePlugin - - -Algebra Integral 1.2.2 VolatilityOraclePlugin plugin - -This contract stores timepoints and calculates adaptive fee and statistical averages - -**Inherits:** AlgebraBasePlugin [IVolatilityOracle](../interfaces/plugins/IVolatilityOracle.md) - -## Public variables -### timepoints -```solidity -struct VolatilityOracle.Timepoint[65536] timepoints -``` -**Selector**: `0x74eceae6` - -Returns data belonging to a certain timepoint - -*Developer note: There is more convenient function to fetch a timepoint: getTimepoints(). Which requires not an index but seconds* - -### timepointIndex -```solidity -uint16 timepointIndex -``` -**Selector**: `0x0786feb6` - -Returns the index of the last timepoint that was written. - - -### lastTimepointTimestamp -```solidity -uint32 lastTimepointTimestamp -``` -**Selector**: `0xf5985d35` - -Returns the timestamp of the last timepoint that was written. - - -### isInitialized -```solidity -bool isInitialized -``` -**Selector**: `0x392e53cd` - -Returns information about whether oracle is initialized - - - -## Functions -### initialize - -```solidity -function initialize() external -``` -**Selector**: `0x8129fc1c` - -Initialize the plugin externally - -*Developer note: This function allows to initialize the plugin if it was created after the pool was created* - -### getSingleTimepoint - -```solidity -function getSingleTimepoint(uint32 secondsAgo) external view returns (int56 tickCumulative, uint88 volatilityCumulative) -``` -**Selector**: `0x88f2e862` - - - -*Developer note: Reverts if a timepoint at or before the desired timepoint timestamp does not exist. -0 may be passed as `secondsAgo' to return the current cumulative values. -If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values -at exactly the timestamp between the two timepoints. -`volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| secondsAgo | uint32 | The amount of time to look back, in seconds, at which point to return a timepoint | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| tickCumulative | int56 | The cumulative tick since the pool was first initialized, as of `secondsAgo` | -| volatilityCumulative | uint88 | The cumulative volatility value since the pool was first initialized, as of `secondsAgo` | - -### getTimepoints - -```solidity -function getTimepoints(uint32[] secondsAgos) external view returns (int56[] tickCumulatives, uint88[] volatilityCumulatives) -``` -**Selector**: `0x9d3a5241` - -Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` - -*Developer note: Reverts if `secondsAgos` > oldest timepoint -`volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| secondsAgos | uint32[] | Each amount of time to look back, in seconds, at which point to return a timepoint | - -**Returns:** - -| Name | Type | Description | -| ---- | ---- | ----------- | -| tickCumulatives | int56[] | The cumulative tick since the pool was first initialized, as of each `secondsAgo` | -| volatilityCumulatives | uint88[] | The cumulative volatility values since the pool was first initialized, as of each `secondsAgo` | - -### prepayTimepointsStorageSlots - -```solidity -function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external -``` -**Selector**: `0xda705235` - -Fills uninitialized timepoints with nonzero value - -*Developer note: Can be used to reduce the gas cost of future swaps* - -| Name | Type | Description | -| ---- | ---- | ----------- | -| startIndex | uint16 | The start index, must be not initialized | -| amount | uint16 | of slots to fill, startIndex + amount must be <= type(uint16).max | - diff --git a/docs/Contracts/Plugin/types/AlgebraFeeConfigurationU144.md b/docs/Contracts/Plugin/types/AlgebraFeeConfigurationU144.md deleted file mode 100644 index 7e626d506..000000000 --- a/docs/Contracts/Plugin/types/AlgebraFeeConfigurationU144.md +++ /dev/null @@ -1,4 +0,0 @@ -# AlgebraFeeConfigurationU144 - - - diff --git a/package.json b/package.json index 2a4cd8617..80848a4e9 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,11 @@ "lint": "prettier \"src/**/contracts/**/*.sol\"", "solhint": "npm run solhint --workspace=src/core --if-present && npm run solhint --workspace=src/periphery --if-present && npm run solhint --workspace=src/plugin --if-present && npm run solhint --workspace=src/farming --if-present", "bootstrap": "npm install && npm run compile", - "compile": "cd src/core && hardhat compile && cd ../periphery && hardhat compile && cd ../plugin && hardhat compile && cd ../farming && hardhat compile", + "compile": "cd src/core && hardhat compile && cd ../periphery && hardhat compile && cd ../farming && hardhat compile", "ci-install": "npm ci", "deploy": "node scripts/deployAll.js", "verify": "node scripts/verifyAll.js", - "docgen": "cd src/core && hardhat docgen && cd ../periphery && hardhat docgen && cd ../plugin && hardhat docgen && cd ../farming && hardhat docgen && node ../../docs/postprocess.js" + "docgen": "cd src/core && hardhat docgen && cd ../periphery && hardhat docgen cd ../farming && hardhat docgen && node ../../docs/postprocess.js" }, "engines": { "npm": ">=10.0.0", diff --git a/scripts/deployAll.js b/scripts/deployAll.js index 879f85bc9..6f32a25d3 100644 --- a/scripts/deployAll.js +++ b/scripts/deployAll.js @@ -4,8 +4,6 @@ const network = process.argv[2]; execSync(`cd src/core && npx hardhat run --network ${network} scripts/deploy.js`, {stdio: 'inherit'}); -execSync(`cd src/plugin && npx hardhat run --network ${network} scripts/deploy.js`, {stdio: 'inherit'}); - execSync(`cd src/periphery && npx hardhat run --network ${network} scripts/deploy.js`, {stdio: 'inherit'}); execSync(`cd src/farming && npx hardhat run --network ${network} scripts/deploy.js`, {stdio: 'inherit'}); \ No newline at end of file diff --git a/scripts/verifyAll.js b/scripts/verifyAll.js index b6fc4348d..13f74a181 100644 --- a/scripts/verifyAll.js +++ b/scripts/verifyAll.js @@ -4,8 +4,6 @@ const network = process.argv[2]; execSync(`cd src/core && npx hardhat run --network ${network} scripts/verify.js`, {stdio: 'inherit'}); -execSync(`cd src/plugin && npx hardhat run --network ${network} scripts/verify.js`, {stdio: 'inherit'}); - execSync(`cd src/periphery && npx hardhat run --network ${network} scripts/verify.js`, {stdio: 'inherit'}); execSync(`cd src/farming && npx hardhat run --network ${network} scripts/verify.js`, {stdio: 'inherit'}); \ No newline at end of file diff --git a/src/README.md b/src/README.md index 266b33bb1..c26378a2c 100644 --- a/src/README.md +++ b/src/README.md @@ -4,10 +4,6 @@ Separate components of the protocol are taken out in modules. The key part of the protocol, which contains the main logic of liquidity pools. Swaps, liquidity provision, flashloans and so on are implemented here. -## Plugin - -A separate module with plugin for the Core module. With the help of replaceable plugins, the functionality of the protocol can be changed and extended. A default plugin from the Algebra protocol team is implemented here. - ## Periphery This module contains smart contracts that can be used by users to easily interact with the protocol. Such as swap router, position manager and so on. diff --git a/src/plugin/.eslintrc.cjs b/src/plugin/.eslintrc.cjs deleted file mode 100644 index d4c034a56..000000000 --- a/src/plugin/.eslintrc.cjs +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - parserOptions: { - project: 'tsconfig.json', - }, -}; diff --git a/src/plugin/.gitignore b/src/plugin/.gitignore deleted file mode 100644 index 2e12a99be..000000000 --- a/src/plugin/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -.env -cache -artifacts -typechain -.idea -bytecode -hashCalc.js -AllBlocks_timestamped.json -results.json \ No newline at end of file diff --git a/src/plugin/.solcover.js b/src/plugin/.solcover.js deleted file mode 100644 index e256b0977..000000000 --- a/src/plugin/.solcover.js +++ /dev/null @@ -1,13 +0,0 @@ -const fs = require('fs'); - -const testContracts = fs.readdirSync("./contracts/test") -const skipFiles = testContracts.map((x) => "test/" + x) - -module.exports = { - skipFiles: skipFiles, - testfiles: "test/*.ts", - mocha: { - grep: "@skip-on-coverage", // Find everything with this tag - invert: true // Run the grep's inverse set. - } - }; \ No newline at end of file diff --git a/src/plugin/.solhintignore b/src/plugin/.solhintignore deleted file mode 100644 index 93c174f7b..000000000 --- a/src/plugin/.solhintignore +++ /dev/null @@ -1 +0,0 @@ -./contracts/test/ \ No newline at end of file diff --git a/src/plugin/LICENSE b/src/plugin/LICENSE deleted file mode 100644 index f72802ec4..000000000 --- a/src/plugin/LICENSE +++ /dev/null @@ -1,98 +0,0 @@ -Business Source License 1.1 - -License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. -"Business Source License" is a trademark of MariaDB Corporation Ab. - ------------------------------------------------------------------------------ - -Parameters - -Licensor: Algebra DAO - -Licensed Work: Algebra Base Plugin V1 - The Licensed Work is (c) 2023 Algebra DAO - -Additional Use Grant: Any uses listed and defined at licenses.algebra.finance - -Change Date: The earlier of 2027-03-15 - -Change License: GNU General Public License v2.0 or later - ------------------------------------------------------------------------------ - -Terms - -The Licensor hereby grants you the right to copy, modify, create derivative -works, redistribute, and make non-production use of the Licensed Work. The -Licensor may make an Additional Use Grant, above, permitting limited -production use. - -Effective on the Change Date, or the fourth anniversary of the first publicly -available distribution of a specific version of the Licensed Work under this -License, whichever comes first, the Licensor hereby grants you rights under -the terms of the Change License, and the rights granted in the paragraph -above terminate. - -If your use of the Licensed Work does not comply with the requirements -currently in effect as described in this License, you must purchase a -commercial license from the Licensor, its affiliated entities, or authorized -resellers, or you must refrain from using the Licensed Work. - -All copies of the original and modified Licensed Work, and derivative works -of the Licensed Work, are subject to this License. This License applies -separately for each version of the Licensed Work and the Change Date may vary -for each version of the Licensed Work released by Licensor. - -You must conspicuously display this License on each original or modified copy -of the Licensed Work. If you receive the Licensed Work in original or -modified form from a third party, the terms and conditions set forth in this -License apply to your use of that work. - -Any use of the Licensed Work in violation of this License will automatically -terminate your rights under this License for the current and all other -versions of the Licensed Work. - -This License does not grant you any right in any trademark or logo of -Licensor or its affiliates (provided that you may use a trademark or logo of -Licensor as expressly required by this License). - -TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON -AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, -EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND -TITLE. - -MariaDB hereby grants you permission to use this License’s text to license -your works, and to refer to it using the trademark "Business Source License", -as long as you comply with the Covenants of Licensor below. - ------------------------------------------------------------------------------ - -Covenants of Licensor - -In consideration of the right to use this License’s text and the "Business -Source License" name and trademark, Licensor covenants to MariaDB, and to all -other recipients of the licensed work to be provided by Licensor: - -1. To specify as the Change License the GPL Version 2.0 or any later version, - or a license that is compatible with GPL Version 2.0 or a later version, - where "compatible" means that software provided under the Change License can - be included in a program with software provided under GPL Version 2.0 or a - later version. Licensor may specify additional Change Licenses without - limitation. - -2. To either: (a) specify an additional grant of rights to use that does not - impose any additional restriction on the right granted in this License, as - the Additional Use Grant; or (b) insert the text "None". - -3. To specify a Change Date. - -4. Not to modify this License in any other way. - ------------------------------------------------------------------------------ - -Notice - -The Business Source License (this document, or the "License") is not an Open -Source license. However, the Licensed Work will eventually be made available -under an Open Source License, as stated in this License. \ No newline at end of file diff --git a/src/plugin/README.md b/src/plugin/README.md deleted file mode 100644 index 33be60adc..000000000 --- a/src/plugin/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Algebra Plugins - Tests status - Echidna status - -This directory contains the base Algebra protocol plugin, which combines the following functionality: -- TWAP oracle -- volatility oracle -- dynamic fee manager -- farmings adapter - -## License - -Licenses for smart contracts are specified in SPDX headers. A key part of the Plugins contracts is under BUSL-1.1 (Business Source License 1.1). - -Interfaces in `./contracts/interfaces` are under GPL-2.0 or later license. diff --git a/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol deleted file mode 100644 index e7eb7c290..000000000 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ /dev/null @@ -1,80 +0,0 @@ -// 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/SlidingFeePlugin.sol'; -import './plugins/VolatilityOraclePlugin.sol'; - -/// @title Algebra Integral 1.2.2 adaptive fee plugin -contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin { - 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, - AlgebraFeeConfiguration memory _config - ) AlgebraBasePlugin(_pool, _factory, _pluginFactory) DynamicFeePlugin(_config) {} - - // ###### 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); - 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); - 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); - } -} diff --git a/src/plugin/contracts/AlgebraBasePluginV2.sol b/src/plugin/contracts/AlgebraBasePluginV2.sol deleted file mode 100644 index 90d0e657a..000000000 --- a/src/plugin/contracts/AlgebraBasePluginV2.sol +++ /dev/null @@ -1,85 +0,0 @@ -// 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/FarmingProxyPlugin.sol'; -import './plugins/SlidingFeePlugin.sol'; -import './plugins/VolatilityOraclePlugin.sol'; - -/// @title Algebra Integral 1.2.2 sliding fee plugin -contract AlgebraBasePluginV2 is SlidingFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin { - 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, - uint16 _baseFee - ) AlgebraBasePlugin(_pool, _factory, _pluginFactory) SlidingFeePlugin(_baseFee) {} - - // ###### 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); - - 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 zeroToOne, - int256, - uint160, - bool, - bytes calldata - ) external override onlyPool returns (bytes4, uint24, uint24) { - (, int24 currentTick, , ) = _getPoolState(); - int24 lastTick = _getLastTick(); - uint16 newFee = _getFeeAndUpdateFactors(zeroToOne, currentTick, lastTick); - - _writeTimepoint(); - return (IAlgebraPlugin.beforeSwap.selector, newFee, 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; - } -} diff --git a/src/plugin/contracts/BasePluginV1Factory.sol b/src/plugin/contracts/BasePluginV1Factory.sol deleted file mode 100644 index 5dbcd0e0d..000000000 --- a/src/plugin/contracts/BasePluginV1Factory.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import './interfaces/IBasePluginV1Factory.sol'; -import './libraries/AdaptiveFee.sol'; -import './AlgebraBasePluginV1.sol'; - -/// @title Algebra Integral 1.2.2 default plugin factory -/// @notice This contract creates Algebra adaptive fee plugins for Algebra liquidity pools -/// @dev This plugin factory can only be used for Algebra base pools -contract BasePluginV1Factory is IBasePluginV1Factory { - /// @inheritdoc IBasePluginV1Factory - bytes32 public constant override ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = keccak256('ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR'); - - /// @inheritdoc IBasePluginV1Factory - address public immutable override algebraFactory; - - /// @inheritdoc IBasePluginV1Factory - AlgebraFeeConfiguration public override defaultFeeConfiguration; // values of constants for sigmoids in fee calculation formula - - /// @inheritdoc IBasePluginV1Factory - address public override farmingAddress; - - /// @inheritdoc IBasePluginV1Factory - 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 IBasePluginV1Factory - 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'); - IDynamicFeeManager volatilityOracle = new AlgebraBasePluginV1(pool, algebraFactory, address(this), defaultFeeConfiguration); - pluginByPool[pool] = address(volatilityOracle); - return address(volatilityOracle); - } - - /// @inheritdoc IBasePluginV1Factory - function setDefaultFeeConfiguration(AlgebraFeeConfiguration calldata newConfig) external override onlyAdministrator { - AdaptiveFee.validateFeeConfiguration(newConfig); - defaultFeeConfiguration = newConfig; - emit DefaultFeeConfiguration(newConfig); - } - - /// @inheritdoc IBasePluginV1Factory - function setFarmingAddress(address newFarmingAddress) external override onlyAdministrator { - require(farmingAddress != newFarmingAddress); - farmingAddress = newFarmingAddress; - emit FarmingAddress(newFarmingAddress); - } -} diff --git a/src/plugin/contracts/BasePluginV2Factory.sol b/src/plugin/contracts/BasePluginV2Factory.sol deleted file mode 100644 index 2afc35f34..000000000 --- a/src/plugin/contracts/BasePluginV2Factory.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import './interfaces/IBasePluginV2Factory.sol'; -import './AlgebraBasePluginV2.sol'; -import './interfaces/plugins/ISlidingFeePlugin.sol'; - -/// @title Algebra Integral 1.2.2 default plugin factory -/// @notice This contract creates Algebra sliding fee plugins for Algebra liquidity pools -/// @dev This plugin factory can only be used for Algebra base pools -contract BasePluginV2Factory is IBasePluginV2Factory { - /// @inheritdoc IBasePluginV2Factory - bytes32 public constant override ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = keccak256('ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR'); - - /// @inheritdoc IBasePluginV2Factory - address public immutable override algebraFactory; - - /// @inheritdoc IBasePluginV2Factory - address public override farmingAddress; - - /// @inheritdoc IBasePluginV2Factory - uint16 public override defaultBaseFee = 3000; - - /// @inheritdoc IBasePluginV2Factory - 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; - } - - /// @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 IBasePluginV2Factory - 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'); - ISlidingFeePlugin plugin = new AlgebraBasePluginV2(pool, algebraFactory, address(this), defaultBaseFee); - pluginByPool[pool] = address(plugin); - return address(plugin); - } - - /// @inheritdoc IBasePluginV2Factory - function setFarmingAddress(address newFarmingAddress) external override onlyAdministrator { - require(farmingAddress != newFarmingAddress); - farmingAddress = newFarmingAddress; - emit FarmingAddress(newFarmingAddress); - } - - /// @inheritdoc IBasePluginV2Factory - function setDefaultBaseFee(uint16 newDefaultBaseFee) external override onlyAdministrator { - require(defaultBaseFee != newDefaultBaseFee); - defaultBaseFee = newDefaultBaseFee; - emit DefaultBaseFee(newDefaultBaseFee); - } -} diff --git a/src/plugin/contracts/README.md b/src/plugin/contracts/README.md deleted file mode 100644 index 1be0da7d1..000000000 --- a/src/plugin/contracts/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## AlgebraBasePluginV1 - -The main plugin contract. Such a contract is created for each liquidity pool and connected to it. The plugin interacts with the pool using hooks (special functions). - -## BasePluginV1Factory - -The contract used to create new `AlgebraBasePluginV1` instances. \ No newline at end of file diff --git a/src/plugin/contracts/base/AlgebraBasePlugin.sol b/src/plugin/contracts/base/AlgebraBasePlugin.sol deleted file mode 100644 index a713ab0c1..000000000 --- a/src/plugin/contracts/base/AlgebraBasePlugin.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import './BasePlugin.sol'; - -/// @title Algebra' internal Integral 1.2.2 plugin base -/// @notice This contract inherits BasePlugin and implements virtual functions -abstract contract AlgebraBasePlugin is BasePlugin { - address internal immutable factory; - - constructor(address _pool, address _factory, address _pluginFactory) BasePlugin(_pool, _pluginFactory) { - factory = _factory; - } - - function _authorize() internal view virtual override { - require(IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); - } -} diff --git a/src/plugin/contracts/base/AlgebraFeeConfiguration.sol b/src/plugin/contracts/base/AlgebraFeeConfiguration.sol deleted file mode 100644 index 3a6b62e2e..000000000 --- a/src/plugin/contracts/base/AlgebraFeeConfiguration.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @notice coefficients for sigmoids: α / (1 + e^( (β-x) / γ)) -/// @dev alpha1 + alpha2 + baseFee must be <= type(uint16).max -struct AlgebraFeeConfiguration { - uint16 alpha1; // max value of the first sigmoid - uint16 alpha2; // max value of the second sigmoid - uint32 beta1; // shift along the x-axis for the first sigmoid - uint32 beta2; // shift along the x-axis for the second sigmoid - uint16 gamma1; // horizontal stretch factor for the first sigmoid - uint16 gamma2; // horizontal stretch factor for the second sigmoid - uint16 baseFee; // minimum possible fee -} diff --git a/src/plugin/contracts/base/BasePlugin.sol b/src/plugin/contracts/base/BasePlugin.sol deleted file mode 100644 index 5cfcffbc8..000000000 --- a/src/plugin/contracts/base/BasePlugin.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity =0.8.20; - -import '@cryptoalgebra/integral-core/contracts/base/common/Timestamp.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/SafeTransfer.sol'; - -import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolState.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; - -import '../interfaces/IBasePlugin.sol'; - -/// @title Algebra Integral 1.2.2 plugin base -/// @notice This contract simplifies development process of plugins by providing base functionality -abstract contract BasePlugin is IBasePlugin, Timestamp { - using Plugins for uint8; - - /// @dev The role can be granted in AlgebraFactory - bytes32 public constant ALGEBRA_BASE_PLUGIN_MANAGER = keccak256('ALGEBRA_BASE_PLUGIN_MANAGER'); - - uint8 private constant defaultPluginConfig = 0; - - address public immutable pool; - address internal immutable pluginFactory; - - modifier onlyPool() { - _checkIfFromPool(); - _; - } - - constructor(address _pool, address _pluginFactory) { - (pool, pluginFactory) = (_pool, _pluginFactory); - } - - function _checkIfFromPool() internal view { - require(msg.sender == pool, 'Only pool can call this'); - } - - function _authorize() internal view virtual; - - 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 IBasePlugin - function collectPluginFee(address token, uint256 amount, address recipient) external override { - _authorize(); - SafeTransfer.safeTransfer(token, recipient, amount); - } - - /// @inheritdoc IAlgebraPlugin - function handlePluginFee(uint256, uint256) external view override onlyPool returns (bytes4) { - return IAlgebraPlugin.handlePluginFee.selector; - } - - // ###### HOOKS ###### - - function beforeInitialize(address, uint160) external virtual override onlyPool returns (bytes4) { - return IAlgebraPlugin.beforeInitialize.selector; - } - - function afterInitialize(address, uint160, int24) external virtual override onlyPool returns (bytes4) { - return IAlgebraPlugin.afterInitialize.selector; - } - - function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external virtual override onlyPool returns (bytes4, uint24) { - return (IAlgebraPlugin.beforeModifyPosition.selector, 0); - } - - function afterModifyPosition( - address, - address, - int24, - int24, - int128, - uint256, - uint256, - bytes calldata - ) external virtual override onlyPool returns (bytes4) { - return IAlgebraPlugin.afterModifyPosition.selector; - } - - function beforeSwap( - address, - address, - bool, - int256, - uint160, - bool, - bytes calldata - ) external virtual override onlyPool returns (bytes4, uint24, uint24) { - return (IAlgebraPlugin.beforeSwap.selector, 0, 0); - } - - function afterSwap(address, address, bool, int256, uint160, int256, int256, bytes calldata) external virtual override onlyPool returns (bytes4) { - return IAlgebraPlugin.afterSwap.selector; - } - - function beforeFlash(address, address, uint256, uint256, bytes calldata) external virtual override onlyPool returns (bytes4) { - return IAlgebraPlugin.beforeFlash.selector; - } - - function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes calldata) external virtual override onlyPool returns (bytes4) { - return IAlgebraPlugin.afterFlash.selector; - } - - function _updatePluginConfigInPool(uint8 newPluginConfig) internal { - (, , , uint8 currentPluginConfig) = _getPoolState(); - if (currentPluginConfig != newPluginConfig) { - IAlgebraPool(pool).setPluginConfig(newPluginConfig); - } - } - - function _disablePluginFlags(uint8 config) internal { - (, , , uint8 currentPluginConfig) = _getPoolState(); - uint8 newPluginConfig = currentPluginConfig & ~config; - if (currentPluginConfig != newPluginConfig) { - IAlgebraPool(pool).setPluginConfig(newPluginConfig); - } - } - - function _enablePluginFlags(uint8 config) internal { - (, , , uint8 currentPluginConfig) = _getPoolState(); - uint8 newPluginConfig = currentPluginConfig | config; - if (currentPluginConfig != newPluginConfig) { - IAlgebraPool(pool).setPluginConfig(newPluginConfig); - } - } -} diff --git a/src/plugin/contracts/base/BasePluginFactory.sol b/src/plugin/contracts/base/BasePluginFactory.sol deleted file mode 100644 index fe9a2f209..000000000 --- a/src/plugin/contracts/base/BasePluginFactory.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import '@cryptoalgebra/integral-periphery/contracts/interfaces/IAlgebraCustomPoolEntryPoint.sol'; -import '../interfaces/IBasePluginFactory.sol'; - -abstract contract BasePluginFactory is IBasePluginFactory { - address public immutable override entryPoint; - - constructor(address _entryPoint) { - entryPoint = _entryPoint; - } - - /// @inheritdoc IBasePluginFactory - function createCustomPool(address creator, address tokenA, address tokenB, bytes calldata data) external virtual returns (address customPool) { - return IAlgebraCustomPoolEntryPoint(entryPoint).createCustomPool(address(this), creator, tokenA, tokenB, data); - } - - /// @inheritdoc IAlgebraPluginFactory - function beforeCreatePoolHook(address pool, address, address, address, address, bytes calldata) external virtual override returns (address) { - require(msg.sender == entryPoint); - return _createPlugin(pool); - } - - /// @inheritdoc IAlgebraPluginFactory - function afterCreatePoolHook(address, address, address) external view virtual override { - require(msg.sender == entryPoint); - } - - function _createPlugin(address pool) internal virtual returns (address); -} diff --git a/src/plugin/contracts/interfaces/IAlgebraVirtualPool.sol b/src/plugin/contracts/interfaces/IAlgebraVirtualPool.sol deleted file mode 100644 index fba6ee905..000000000 --- a/src/plugin/contracts/interfaces/IAlgebraVirtualPool.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title The interface for the virtual pool -/// @dev Used to calculate active liquidity in farmings -interface IAlgebraVirtualPool { - /// @dev This function is called by the main pool if an initialized ticks are crossed by swap. - /// If any one of crossed ticks is also initialized in a virtual pool it should be crossed too - /// @param targetTick The target tick up to which we need to cross all active ticks - /// @param zeroToOne Swap direction - function crossTo(int24 targetTick, bool zeroToOne) external returns (bool success); -} diff --git a/src/plugin/contracts/interfaces/IBasePlugin.sol b/src/plugin/contracts/interfaces/IBasePlugin.sol deleted file mode 100644 index c31d3fecd..000000000 --- a/src/plugin/contracts/interfaces/IBasePlugin.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; -pragma abicoder v2; - -import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol'; - -/// @title The interface for the BasePlugin -interface IBasePlugin is IAlgebraPlugin { - /// @notice Claim plugin fee - /// @param token The token address - /// @param amount Amount of tokens - /// @param recipient Recipient address - function collectPluginFee(address token, uint256 amount, address recipient) external; -} diff --git a/src/plugin/contracts/interfaces/IBasePluginFactory.sol b/src/plugin/contracts/interfaces/IBasePluginFactory.sol deleted file mode 100644 index b6840d3a2..000000000 --- a/src/plugin/contracts/interfaces/IBasePluginFactory.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -import '@cryptoalgebra/integral-periphery/contracts/interfaces/IAlgebraCustomPoolEntryPoint.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol'; - -/// @title The interface for the BasePluginFactory -interface IBasePluginFactory is IAlgebraPluginFactory { - /// @notice Returns the address of AlgebraCustomPoolEntryPoint - /// @dev This is a main entry point for creating, managing plugins - /// @return The AlgebraCustomPoolEntryPoint contract address - function entryPoint() external view returns (address); - - /// @notice Create a custom pool with a plugin - /// @param creator The address that initiated the pool creation - /// @param tokenA The address of first token in pool - /// @param tokenB The address of second token in pool - /// @param data The data to be passed to beforeCreatePoolHook - /// @return customPool The address of created plugin - function createCustomPool(address creator, address tokenA, address tokenB, bytes calldata data) external returns (address customPool); - - /// @notice Sets tick spacing in a deployed custom pool - /// @param pool The address of custom pool - /// @param newTickSpacing The new tick spacing - function setTickSpacing(address pool, int24 newTickSpacing) external; - - /// @notice Sets plugin in a deployed custom pool - /// @param pool The address of custom pool - /// @param newPluginAddress The new plugin - function setPlugin(address pool, address newPluginAddress) external; - - /// @notice Sets plugin config in a deployed custom pool - /// @param pool The address of custom pool - /// @param newConfig The new config - function setPluginConfig(address pool, uint8 newConfig) external; - - /// @notice Sets fee in a deployed custom pool - /// @param pool The address of custom pool - /// @param newFee The new fee - function setFee(address pool, uint16 newFee) external; -} diff --git a/src/plugin/contracts/interfaces/IBasePluginV1Factory.sol b/src/plugin/contracts/interfaces/IBasePluginV1Factory.sol deleted file mode 100644 index 495a523c1..000000000 --- a/src/plugin/contracts/interfaces/IBasePluginV1Factory.sol +++ /dev/null @@ -1,61 +0,0 @@ -// 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 IBasePluginV1Factory 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 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 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; -} diff --git a/src/plugin/contracts/interfaces/IBasePluginV2Factory.sol b/src/plugin/contracts/interfaces/IBasePluginV2Factory.sol deleted file mode 100644 index 77597ffc3..000000000 --- a/src/plugin/contracts/interfaces/IBasePluginV2Factory.sol +++ /dev/null @@ -1,46 +0,0 @@ -// 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'; - -/// @title The interface for the BasePluginV2Factory -/// @notice This contract creates Algebra default plugins for Algebra liquidity pools -interface IBasePluginV2Factory is IAlgebraPluginFactory { - /// @notice Emitted when the farming address is changed - /// @param newFarmingAddress The farming address after the address was changed - event FarmingAddress(address newFarmingAddress); - - event DefaultBaseFee(uint16 newDefaultBaseFee); - - /// @notice The hash of 'ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR' used as role - /// @dev allows to change settings of BasePluginV2Factory - 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 current farming address - /// @return The farming contract address - function farmingAddress() external view returns (address); - - function defaultBaseFee() external view returns (uint16); - - /// @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); - - /// @dev updates farmings manager address on the factory - /// @param newFarmingAddress The new tokenomics contract address - function setFarmingAddress(address newFarmingAddress) external; - - function setDefaultBaseFee(uint16 newDefaultBaseFee) external; -} \ No newline at end of file diff --git a/src/plugin/contracts/interfaces/plugins/IDynamicFeeManager.sol b/src/plugin/contracts/interfaces/plugins/IDynamicFeeManager.sol deleted file mode 100644 index 6a52594c3..000000000 --- a/src/plugin/contracts/interfaces/plugins/IDynamicFeeManager.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; -pragma abicoder v2; - -import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraDynamicFeePlugin.sol'; -import '../../base/AlgebraFeeConfiguration.sol'; - -/// @title The interface for the Algebra dynamic fee manager -/// @dev This contract calculates adaptive fee -interface IDynamicFeeManager is IAlgebraDynamicFeePlugin { - /// @notice Emitted when the fee configuration is changed - /// @param feeConfig The structure with dynamic fee parameters - /// @dev See the AdaptiveFee struct for more details - event FeeConfiguration(AlgebraFeeConfiguration feeConfig); - - /// @notice Current dynamic fee configuration - /// @dev See the AdaptiveFee struct for more details - function feeConfig() external view returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee); - - /// @notice Changes fee configuration for the pool - function changeFeeConfiguration(AlgebraFeeConfiguration calldata feeConfig) external; -} diff --git a/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol b/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol deleted file mode 100644 index b914a99c5..000000000 --- a/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title The interface for the Algebra farming plugin -/// @dev This contract used for virtual pools in farms -interface IFarmingPlugin { - /// @notice Emitted when new activeIncentive is set - /// @param newIncentive The address of the new incentive - event Incentive(address newIncentive); - - /// @notice Connects or disconnects an incentive. - /// @dev Only farming can connect incentives. - /// The one who connected it and the current farming has the right to disconnect the incentive. - /// @param newIncentive The address associated with the incentive or zero address - function setIncentive(address newIncentive) external; - - /// @notice Checks if the incentive is connected to pool - /// @dev Returns false if the plugin has a different incentive set, the plugin is not connected to the pool, - /// or the plugin configuration is incorrect. - /// @param targetIncentive The address of the incentive to be checked - /// @return Indicates whether the target incentive is active - function isIncentiveConnected(address targetIncentive) external view returns (bool); - - /// @notice Returns the address of active incentive - /// @dev if there is no active incentive at the moment, incentiveAddress would be equal to address(0) - /// @return The address associated with the current active incentive - function incentive() external view returns (address); - - /// @notice Returns the address of the pool the plugin is created for - /// @return address of the pool - function getPool() external view returns (address); -} diff --git a/src/plugin/contracts/interfaces/plugins/ISlidingFeePlugin.sol b/src/plugin/contracts/interfaces/plugins/ISlidingFeePlugin.sol deleted file mode 100644 index 723cc0d5b..000000000 --- a/src/plugin/contracts/interfaces/plugins/ISlidingFeePlugin.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -interface ISlidingFeePlugin { - event PriceChangeFactor(uint256 priceChangeFactor); - event BaseFee(uint16 baseFee); - - function setBaseFee(uint16 newBaseFee) external; - - function setPriceChangeFactor(uint16 newPriceChangeFactor) external; -} \ No newline at end of file diff --git a/src/plugin/contracts/interfaces/plugins/IVolatilityOracle.sol b/src/plugin/contracts/interfaces/plugins/IVolatilityOracle.sol deleted file mode 100644 index 265f937bf..000000000 --- a/src/plugin/contracts/interfaces/plugins/IVolatilityOracle.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title The interface for the Algebra volatility oracle -/// @dev This contract stores timepoints and calculates statistical averages -interface IVolatilityOracle { - /// @notice Returns data belonging to a certain timepoint - /// @param index The index of timepoint in the array - /// @dev There is more convenient function to fetch a timepoint: getTimepoints(). Which requires not an index but seconds - /// @return initialized Whether the timepoint has been initialized and the values are safe to use - /// @return blockTimestamp The timestamp of the timepoint - /// @return tickCumulative The tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp - /// @return volatilityCumulative Cumulative standard deviation for the life of the pool as of the timepoint timestamp - /// @return tick The tick at blockTimestamp - /// @return averageTick Time-weighted average tick - /// @return windowStartIndex Index of closest timepoint >= WINDOW seconds ago - function timepoints( - uint256 index - ) - external - view - returns ( - bool initialized, - uint32 blockTimestamp, - int56 tickCumulative, - uint88 volatilityCumulative, - int24 tick, - int24 averageTick, - uint16 windowStartIndex - ); - - /// @notice Returns the index of the last timepoint that was written. - /// @return index of the last timepoint written - function timepointIndex() external view returns (uint16); - - /// @notice Initialize the plugin externally - /// @dev This function allows to initialize the plugin if it was created after the pool was created - function initialize() external; - - /// @notice Returns the timestamp of the last timepoint that was written. - /// @return timestamp of the last timepoint - function lastTimepointTimestamp() external view returns (uint32); - - /// @notice Returns information about whether oracle is initialized - /// @return true if oracle is initialized, otherwise false - function isInitialized() external view returns (bool); - - /// @dev Reverts if a timepoint at or before the desired timepoint timestamp does not exist. - /// 0 may be passed as `secondsAgo' to return the current cumulative values. - /// If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values - /// at exactly the timestamp between the two timepoints. - /// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors - /// @param secondsAgo The amount of time to look back, in seconds, at which point to return a timepoint - /// @return tickCumulative The cumulative tick since the pool was first initialized, as of `secondsAgo` - /// @return volatilityCumulative The cumulative volatility value since the pool was first initialized, as of `secondsAgo` - function getSingleTimepoint(uint32 secondsAgo) external view returns (int56 tickCumulative, uint88 volatilityCumulative); - - /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` - /// @dev Reverts if `secondsAgos` > oldest timepoint - /// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors - /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return a timepoint - /// @return tickCumulatives The cumulative tick since the pool was first initialized, as of each `secondsAgo` - /// @return volatilityCumulatives The cumulative volatility values since the pool was first initialized, as of each `secondsAgo` - function getTimepoints(uint32[] memory secondsAgos) external view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives); - - /// @notice Fills uninitialized timepoints with nonzero value - /// @dev Can be used to reduce the gas cost of future swaps - /// @param startIndex The start index, must be not initialized - /// @param amount of slots to fill, startIndex + amount must be <= type(uint16).max - function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external; -} diff --git a/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol b/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol deleted file mode 100644 index 5bc1a0365..000000000 --- a/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity =0.8.20; -pragma abicoder v1; - -import '../interfaces/plugins/IVolatilityOracle.sol'; -import '../interfaces/IBasePluginV1Factory.sol'; -import './IAlgebraOracleV1TWAP.sol'; - -import '../libraries/integration/OracleLibrary.sol'; - -/// @title Algebra Integral 1.2.2 base plugin V1 oracle frontend -/// @notice Provides data from oracle corresponding pool -/// @dev These functions are not very gas efficient and it is better not to use them on-chain -contract AlgebraOracleV1TWAP is IAlgebraOracleV1TWAP { - /// @inheritdoc IAlgebraOracleV1TWAP - address public immutable override pluginFactory; - - constructor(address _pluginFactory) { - pluginFactory = _pluginFactory; - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function getQuoteAtTick( - int24 tick, - uint128 baseAmount, - address baseToken, - address quoteToken - ) external pure override returns (uint256 quoteAmount) { - return OracleLibrary.getQuoteAtTick(tick, baseAmount, baseToken, quoteToken); - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function getAverageTick(address pool, uint32 period) external view override returns (int24 timeWeightedAverageTick, bool isConnected) { - address oracleAddress = _getPluginForPool(pool); - timeWeightedAverageTick = OracleLibrary.consult(oracleAddress, period); - isConnected = OracleLibrary.isOracleConnectedToPool(oracleAddress, pool); - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function latestTimestamp(address pool) external view override returns (uint32) { - return IVolatilityOracle(_getPluginForPool(pool)).lastTimepointTimestamp(); - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function oldestTimestamp(address pool) external view override returns (uint32 _oldestTimestamp) { - address oracle = _getPluginForPool(pool); - (, _oldestTimestamp) = OracleLibrary.oldestTimepointMetadata(oracle); - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function latestIndex(address pool) external view override returns (uint16) { - return OracleLibrary.latestIndex(_getPluginForPool(pool)); - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function isOracleConnected(address pool) external view override returns (bool connected) { - connected = OracleLibrary.isOracleConnectedToPool(_getPluginForPool(pool), pool); - } - - /// @inheritdoc IAlgebraOracleV1TWAP - function oldestIndex(address pool) external view override returns (uint16 _oldestIndex) { - address oracle = _getPluginForPool(pool); - (_oldestIndex, ) = OracleLibrary.oldestTimepointMetadata(oracle); - } - - function _getPluginForPool(address pool) internal view returns (address) { - address pluginAddress = IBasePluginV1Factory(pluginFactory).pluginByPool(pool); - require(pluginAddress != address(0), 'Oracle does not exist'); - return pluginAddress; - } -} diff --git a/src/plugin/contracts/lens/IAlgebraOracleV1TWAP.sol b/src/plugin/contracts/lens/IAlgebraOracleV1TWAP.sol deleted file mode 100644 index d675b02d4..000000000 --- a/src/plugin/contracts/lens/IAlgebraOracleV1TWAP.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title Algebra base plugin V1 oracle frontend -/// @notice Provides data from oracle corresponding pool -/// @dev These functions are not very gas efficient and it is better not to use them on-chain -interface IAlgebraOracleV1TWAP { - /// @notice The address of the factory of plugins that are used as oracles by this contract - function pluginFactory() external view returns (address); - - /// @notice Given a tick and a token amount, calculates the amount of token received in exchange - /// @dev Should not be used as quote for swap - /// @param tick Tick value used to calculate the quote - /// @param baseAmount Amount of token to be converted - /// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination - /// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination - /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken - function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) external pure returns (uint256 quoteAmount); - - /// @notice Fetches time-weighted average tick using Algebra VolatilityOracle - /// @dev Oracle may stop receiving data from the pool (be disconnected). For that reason it is important - /// not to rely on the absolute accuracy and availability at any time of this oracle. - /// It is recommended to check the latest available timestamp using the `latestTimestamp` method and don't use the data if the last entry is too old - /// @param pool The address of Algebra Integral pool - /// @param period Number of seconds in the past to start calculating time-weighted average - /// @return timeWeightedAverageTick The time-weighted average tick from (block.timestamp - period) to block.timestamp - /// @return isConnected Is oracle currently connected to the pool. If disconnected data can be obsolete - function getAverageTick(address pool, uint32 period) external view returns (int24 timeWeightedAverageTick, bool isConnected); - - /// @notice Returns the last timestamp written in the oracle - function latestTimestamp(address pool) external view returns (uint32); - - /// @notice Returns the oldest timestamp available in the oracle - function oldestTimestamp(address pool) external view returns (uint32 _oldestTimestamp); - - /// @notice Returns the index of last record written in the oracle - function latestIndex(address pool) external view returns (uint16); - - /// @notice Returns the index of oldest record available in the oracle - function oldestIndex(address pool) external view returns (uint16); - - /// @notice Whether or not the oracle is connected to the liquidity pool - /// @dev Oracle should not be used if disconnected from pool - function isOracleConnected(address pool) external view returns (bool connected); -} diff --git a/src/plugin/contracts/libraries/AdaptiveFee.sol b/src/plugin/contracts/libraries/AdaptiveFee.sol deleted file mode 100644 index b2ad007b2..000000000 --- a/src/plugin/contracts/libraries/AdaptiveFee.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import '@cryptoalgebra/integral-core/contracts/libraries/Constants.sol'; -import '../base/AlgebraFeeConfiguration.sol'; -import '../types/AlgebraFeeConfigurationU144.sol'; - -/// @title AdaptiveFee -/// @notice Calculates fee based on combination of sigmoids -/// @dev Version for AlgebraBasePluginV1 -library AdaptiveFee { - uint16 internal constant INITIAL_MIN_FEE = 0.01e4; // 0.01% - - /// @notice Returns default initial fee configuration - function initialFeeConfiguration() internal pure returns (AlgebraFeeConfiguration memory) { - return - AlgebraFeeConfiguration({ - alpha1: 3000 - INITIAL_MIN_FEE, // max value of the first sigmoid in hundredths of a bip, i.e. 1e-6 - alpha2: 15000 - 3000, // max value of the second sigmoid in hundredths of a bip, i.e. 1e-6 - beta1: 360, // shift along the x-axis (volatility) for the first sigmoid - beta2: 60000, // shift along the x-axis (volatility) for the second sigmoid - gamma1: 59, // horizontal stretch factor for the first sigmoid - gamma2: 8500, // horizontal stretch factor for the second sigmoid - baseFee: INITIAL_MIN_FEE // in hundredths of a bip, i.e. 1e-6 - }); - } - - /// @notice Validates fee configuration. - /// @dev Maximum fee value capped by baseFee + alpha1 + alpha2 must be <= type(uint16).max - /// gammas must be > 0 - function validateFeeConfiguration(AlgebraFeeConfiguration memory _config) internal pure { - require(uint256(_config.alpha1) + uint256(_config.alpha2) + uint256(_config.baseFee) <= type(uint16).max, 'Max fee exceeded'); - require(_config.gamma1 != 0 && _config.gamma2 != 0, 'Gammas must be > 0'); - } - - /// @notice Calculates fee based on formula: - /// baseFee + sigmoid1(volatility) + sigmoid2(volatility) - /// maximum value capped by baseFee + alpha1 + alpha2 - function getFee(uint88 volatility, AlgebraFeeConfigurationU144 config) internal pure returns (uint16 fee) { - unchecked { - volatility /= 15; // normalize for 15 sec interval - uint256 sumOfSigmoids = sigmoid(volatility, config.gamma1(), config.alpha1(), config.beta1()) + - sigmoid(volatility, config.gamma2(), config.alpha2(), config.beta2()); - - uint256 result = uint256(config.baseFee()) + sumOfSigmoids; - assert(result <= type(uint16).max); // should always be true - - return uint16(result); // safe since alpha1 + alpha2 + baseFee _must_ be <= type(uint16).max - } - } - - /// @notice calculates α / (1 + e^( (β-x) / γ)) - /// that is a sigmoid with a maximum value of α, x-shifted by β, and stretched by γ - /// @dev returns uint256 for fuzzy testing. Guaranteed that the result is not greater than alpha - function sigmoid(uint256 x, uint16 g, uint16 alpha, uint256 beta) internal pure returns (uint256 res) { - unchecked { - if (x > beta) { - x = x - beta; - if (x >= 6 * uint256(g)) return alpha; // so x < 19 bits - uint256 g4 = uint256(g) ** 4; // < 64 bits (4*16) - uint256 ex = expXg4(x, g, g4); // < 155 bits - res = (alpha * ex) / (g4 + ex); // in worst case: (16 + 155 bits) / 155 bits - // so res <= alpha - } else { - x = beta - x; - if (x >= 6 * uint256(g)) return 0; // so x < 19 bits - uint256 g4 = uint256(g) ** 4; // < 64 bits (4*16) - uint256 ex = g4 + expXg4(x, g, g4); // < 156 bits - res = (alpha * g4) / ex; // in worst case: (16 + 128 bits) / 156 bits - // g8 <= ex, so res <= alpha - } - } - } - - /// @notice calculates e^(x/g) * g^4 in a series, since (around zero): - /// e^x = 1 + x + x^2/2 + ... + x^n/n! + ... - /// e^(x/g) = 1 + x/g + x^2/(2*g^2) + ... + x^(n)/(g^n * n!) + ... - /// @dev has good accuracy only if x/g < 6 - function expXg4(uint256 x, uint16 g, uint256 gHighestDegree) internal pure returns (uint256 res) { - uint256 closestValue; // nearest 'table' value of e^(x/g), multiplied by 10^20 - assembly { - let xdg := div(x, g) - switch xdg - case 0 { - closestValue := 100000000000000000000 // 1 - } - case 1 { - closestValue := 271828182845904523536 // ~= e - } - case 2 { - closestValue := 738905609893065022723 // ~= e^2 - } - case 3 { - closestValue := 2008553692318766774092 // ~= e^3 - } - case 4 { - closestValue := 5459815003314423907811 // ~= e^4 - } - default { - closestValue := 14841315910257660342111 // ~= e^5 - } - - x := mod(x, g) - } - - unchecked { - if (x >= g / 2) { - // (x - closestValue) >= 0.5, so closestValue := closestValue * e^0.5 - x -= g / 2; - closestValue = (closestValue * 164872127070012814684) / 1e20; - } - - // After calculating the closestValue x/g is <= 0.5, so that the series in the neighborhood of zero converges with sufficient speed - uint256 xLowestDegree = x; - res = gHighestDegree; // g**4, res < 64 bits - - gHighestDegree /= g; // g**3 - res += xLowestDegree * gHighestDegree; // g**4 + x*g**3, res < 68 - - gHighestDegree /= g; // g**2 - xLowestDegree *= x; // x**2 - // g**4 + x * g**3 + (x**2 * g**2) / 2, res < 71 - res += (xLowestDegree * gHighestDegree) / 2; - - gHighestDegree /= g; // g - xLowestDegree *= x; // x**3 - // g^4 + x * g^3 + (x^2 * g^2)/2 + x^3(g*4 + x)/24, res < 73 - res += (xLowestDegree * g * 4 + xLowestDegree * x) / 24; - - // res = g^4 * (1 + x/g + x^2/(2*g^2) + x^3/(6*g^3) + x^4/(24*g^4)) * closestValue / 10^20, closestValue < 75 bits, res < 155 - res = (res * closestValue) / (1e20); - } - } -} diff --git a/src/plugin/contracts/libraries/VolatilityOracle.sol b/src/plugin/contracts/libraries/VolatilityOracle.sol deleted file mode 100644 index d65dafeb3..000000000 --- a/src/plugin/contracts/libraries/VolatilityOracle.sol +++ /dev/null @@ -1,556 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -/// @title VolatilityOracle -/// @notice Provides price and volatility data useful for a wide variety of system designs -/// @dev Instances of stored oracle data, "timepoints", are collected in the oracle array -/// Timepoints are overwritten when the full length of the timepoints array is populated. -/// The most recent timepoint is available by passing 0 to getSingleTimepoint(). -library VolatilityOracle { - /// @notice `target` timestamp is older than oldest timepoint - error targetIsTooOld(); - - /// @notice oracle is initialized already - error volatilityOracleAlreadyInitialized(); - - uint32 internal constant WINDOW = 1 days; - uint256 private constant UINT16_MODULO = 65536; - - struct Timepoint { - bool initialized; // whether or not the timepoint is initialized - uint32 blockTimestamp; // the block timestamp of the timepoint - int56 tickCumulative; // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized - uint88 volatilityCumulative; // the volatility accumulator; overflow after ~34800 years is desired :) - int24 tick; // tick at this blockTimestamp - int24 averageTick; // average tick at this blockTimestamp (for WINDOW seconds) - uint16 windowStartIndex; // closest timepoint lte WINDOW seconds ago (or oldest timepoint), _should be used only from last timepoint_! - } - - /// @notice Initialize the timepoints array by writing the first slot. Called once for the lifecycle of the timepoints array - /// @param self The stored timepoints array - /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32 - /// @param tick Initial tick - function initialize(Timepoint[UINT16_MODULO] storage self, uint32 time, int24 tick) internal { - Timepoint storage _zero = self[0]; - if (_zero.initialized) revert volatilityOracleAlreadyInitialized(); - (_zero.initialized, _zero.blockTimestamp, _zero.tick, _zero.averageTick) = (true, time, tick, tick); - } - - /// @notice Writes a timepoint to the array - /// @dev Writable at most once per block. `lastIndex` must be tracked externally. - /// @param self The stored timepoints array - /// @param lastIndex The index of the timepoint that was most recently written to the timepoints array - /// @param blockTimestamp The timestamp of the new timepoint - /// @param tick The active tick at the time of the new timepoint - /// @return indexUpdated The new index of the most recently written element in the timepoints array - /// @return oldestIndex The new index of the oldest timepoint - function write( - Timepoint[UINT16_MODULO] storage self, - uint16 lastIndex, - uint32 blockTimestamp, - int24 tick - ) internal returns (uint16 indexUpdated, uint16 oldestIndex) { - Timepoint memory last = self[lastIndex]; - // early return if we've already written a timepoint this block - if (last.blockTimestamp == blockTimestamp) return (lastIndex, 0); - - // get next index considering overflow - unchecked { - indexUpdated = lastIndex + 1; - } - - // check if we have overflow in the past - if (self[indexUpdated].initialized) oldestIndex = indexUpdated; - - (int24 avgTick, uint16 windowStartIndex) = _getAverageTickCasted( - self, - blockTimestamp, - tick, - lastIndex, - oldestIndex, - last.blockTimestamp, - last.tickCumulative - ); - unchecked { - // overflow of indexes is desired - if (windowStartIndex == indexUpdated) windowStartIndex++; // important, since this value can be used to narrow the search - self[indexUpdated] = _createNewTimepoint(last, blockTimestamp, tick, avgTick, windowStartIndex); - if (oldestIndex == indexUpdated) oldestIndex++; // previous oldest index has been overwritten - } - } - - /// @dev Reverts if a timepoint at or before the desired timepoint timestamp does not exist. - /// 0 may be passed as `secondsAgo' to return the current cumulative values. - /// If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values - /// at exactly the timestamp between the two timepoints. - /// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors - /// @param self The stored timepoints array - /// @param time The current block timestamp - /// @param secondsAgo The amount of time to look back, in seconds, at which point to return a timepoint - /// @param tick The current tick - /// @param lastIndex The index of the timepoint that was most recently written to the timepoints array - /// @param oldestIndex The index of the oldest timepoint - /// @return targetTimepoint desired timepoint or it's interpolation - function getSingleTimepoint( - Timepoint[UINT16_MODULO] storage self, - uint32 time, - uint32 secondsAgo, - int24 tick, - uint16 lastIndex, - uint16 oldestIndex - ) internal view returns (Timepoint memory targetTimepoint) { - unchecked { - uint32 target = time - secondsAgo; - (Timepoint storage beforeOrAt, Timepoint storage atOrAfter, bool samePoint, ) = _getTimepointsAt(self, time, target, lastIndex, oldestIndex); - - targetTimepoint = beforeOrAt; - if (target == targetTimepoint.blockTimestamp) return targetTimepoint; // we're at the left boundary - if (samePoint) { - // if target is newer than last timepoint - (int24 avgTick, uint16 windowStartIndex) = _getAverageTickCasted( - self, - target, - tick, - lastIndex, - oldestIndex, - targetTimepoint.blockTimestamp, - targetTimepoint.tickCumulative - ); - return _createNewTimepoint(targetTimepoint, target, tick, avgTick, windowStartIndex); - } - - (uint32 timestampAfter, int56 tickCumulativeAfter) = (atOrAfter.blockTimestamp, atOrAfter.tickCumulative); - if (target == timestampAfter) return atOrAfter; // we're at the right boundary - - // we're in the middle - (uint32 timepointTimeDelta, uint32 targetDelta) = (timestampAfter - targetTimepoint.blockTimestamp, target - targetTimepoint.blockTimestamp); - - targetTimepoint.tickCumulative += - ((tickCumulativeAfter - targetTimepoint.tickCumulative) / int56(uint56(timepointTimeDelta))) * - int56(uint56(targetDelta)); - targetTimepoint.volatilityCumulative += - ((atOrAfter.volatilityCumulative - targetTimepoint.volatilityCumulative) / timepointTimeDelta) * - targetDelta; - } - } - - /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` - /// @dev Reverts if `secondsAgos` > oldest timepoint - /// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors - /// @param self The stored timepoints array - /// @param currentTime The current block.timestamp - /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return a timepoint - /// @param tick The current tick - /// @param lastIndex The index of the timepoint that was most recently written to the timepoints array - /// @return tickCumulatives The cumulative time-weighted tick since the pool was first initialized, as of each `secondsAgo` - /// @return volatilityCumulatives The cumulative volatility values since the pool was first initialized, as of each `secondsAgo` - function getTimepoints( - Timepoint[UINT16_MODULO] storage self, - uint32 currentTime, - uint32[] memory secondsAgos, - int24 tick, - uint16 lastIndex - ) internal view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { - uint256 secondsLength = secondsAgos.length; - tickCumulatives = new int56[](secondsLength); - volatilityCumulatives = new uint88[](secondsLength); - - uint16 oldestIndex = getOldestIndex(self, lastIndex); - Timepoint memory current; - unchecked { - for (uint256 i; i < secondsLength; ++i) { - current = getSingleTimepoint(self, currentTime, secondsAgos[i], tick, lastIndex, oldestIndex); - (tickCumulatives[i], volatilityCumulatives[i]) = (current.tickCumulative, current.volatilityCumulative); - } - } - } - - /// @notice Returns the index of the oldest timepoint - /// @param self The stored timepoints array - /// @param lastIndex The index of the timepoint that was most recently written to the timepoints array - /// @return oldestIndex The index of the oldest timepoint - function getOldestIndex(Timepoint[UINT16_MODULO] storage self, uint16 lastIndex) internal view returns (uint16 oldestIndex) { - unchecked { - uint16 nextIndex = lastIndex + 1; // considering overflow - if (self[nextIndex].initialized) oldestIndex = nextIndex; // check if we have overflow in the past - } - } - - /// @notice Returns average volatility in the range from currentTime-WINDOW to currentTime - /// @param self The stored timepoints array - /// @param currentTime The current block.timestamp - /// @param tick The current tick - /// @param lastIndex The index of the timepoint that was most recently written to the timepoints array - /// @param oldestIndex The index of the oldest timepoint - /// @return volatilityAverage The average volatility in the recent range - function getAverageVolatility( - Timepoint[UINT16_MODULO] storage self, - uint32 currentTime, - int24 tick, - uint16 lastIndex, - uint16 oldestIndex - ) internal view returns (uint88 volatilityAverage) { - unchecked { - Timepoint storage lastTimepoint = self[lastIndex]; - bool timeAtLastTimepoint = lastTimepoint.blockTimestamp == currentTime; - uint88 lastCumulativeVolatility = lastTimepoint.volatilityCumulative; - uint16 windowStartIndex = lastTimepoint.windowStartIndex; // index of timepoint before of at lastTimepoint.blockTimestamp - WINDOW - - if (!timeAtLastTimepoint) { - lastCumulativeVolatility = _getVolatilityCumulativeAt(self, currentTime, 0, tick, lastIndex, oldestIndex); - } - - uint32 oldestTimestamp = self[oldestIndex].blockTimestamp; - if (_lteConsideringOverflow(oldestTimestamp, currentTime - WINDOW, currentTime)) { - // oldest timepoint is earlier than 24 hours ago - uint88 cumulativeVolatilityAtStart; - if (timeAtLastTimepoint) { - // interpolate cumulative volatility to avoid search. Since the last timepoint has _just_ been written, we know for sure - // that the start of the window is between windowStartIndex and windowStartIndex + 1 - (oldestTimestamp, cumulativeVolatilityAtStart) = (self[windowStartIndex].blockTimestamp, self[windowStartIndex].volatilityCumulative); - - uint32 timeDeltaBetweenPoints = self[windowStartIndex + 1].blockTimestamp - oldestTimestamp; - - cumulativeVolatilityAtStart += - ((self[windowStartIndex + 1].volatilityCumulative - cumulativeVolatilityAtStart) * (currentTime - WINDOW - oldestTimestamp)) / - timeDeltaBetweenPoints; - } else { - cumulativeVolatilityAtStart = _getVolatilityCumulativeAt(self, currentTime, WINDOW, tick, lastIndex, oldestIndex); - } - - return ((lastCumulativeVolatility - cumulativeVolatilityAtStart) / WINDOW); // sample is big enough to ignore bias of variance - } else if (currentTime != oldestTimestamp) { - // recorded timepoints are not enough, so we will extrapolate - uint88 _oldestVolatilityCumulative = self[oldestIndex].volatilityCumulative; - uint32 unbiasedDenominator = currentTime - oldestTimestamp; - if (unbiasedDenominator > 1) unbiasedDenominator--; // Bessel's correction for "small" sample - return ((lastCumulativeVolatility - _oldestVolatilityCumulative) / unbiasedDenominator); - } - } - } - - // ##### further functions are private to the library, but some are made internal for fuzzy testing ##### - - /// @notice Transforms a previous timepoint into a new timepoint, given the passage of time and the current tick and liquidity values - /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows - /// @dev The function changes the structure given to the input, and does not create a new one - /// @param last The specified timepoint to be used in creation of new timepoint - /// @param blockTimestamp The timestamp of the new timepoint - /// @param tick The active tick at the time of the new timepoint - /// @param averageTick The average tick at the time of the new timepoint - /// @param windowStartIndex The index of closest timepoint >= WINDOW seconds ago - /// @return Timepoint The newly populated timepoint - function _createNewTimepoint( - Timepoint memory last, - uint32 blockTimestamp, - int24 tick, - int24 averageTick, - uint16 windowStartIndex - ) internal pure returns (Timepoint memory) { - unchecked { - uint32 delta = blockTimestamp - last.blockTimestamp; // overflow is desired - // We don't create a new structure in memory to save gas. Therefore, the function changes the old structure - last.initialized = true; - last.blockTimestamp = blockTimestamp; - last.tickCumulative += int56(tick) * int56(uint56(delta)); - last.volatilityCumulative += uint88(_volatilityOnRange(int256(uint256(delta)), tick, tick, last.averageTick, averageTick)); // always fits 88 bits - last.tick = tick; - last.averageTick = averageTick; - last.windowStartIndex = windowStartIndex; - return last; - } - } - - /// @notice Calculates volatility between two sequential timepoints with resampling to 1 sec frequency - /// @param dt Timedelta between timepoints, must be within uint32 range - /// @param tick0 The tick after the left timepoint, must be within int24 range - /// @param tick1 The tick at the right timepoint, must be within int24 range - /// @param avgTick0 The average tick at the left timepoint, must be within int24 range - /// @param avgTick1 The average tick at the right timepoint, must be within int24 range - /// @return volatility The volatility between two sequential timepoints - /// If the requirements for the parameters are met, it always fits 88 bits - function _volatilityOnRange(int256 dt, int256 tick0, int256 tick1, int256 avgTick0, int256 avgTick1) internal pure returns (uint256 volatility) { - // On the time interval from the previous timepoint to the current - // we can represent tick and average tick change as two straight lines: - // tick = k*t + b, where k and b are some constants - // avgTick = p*t + q, where p and q are some constants - // we want to get sum of (tick(t) - avgTick(t))^2 for every t in the interval (0; dt] - // so: (tick(t) - avgTick(t))^2 = ((k*t + b) - (p*t + q))^2 = (k-p)^2 * t^2 + 2(k-p)(b-q)t + (b-q)^2 - // since everything except t is a constant, we need to use progressions for t and t^2: - // sum(t) for t from 1 to dt = dt*(dt + 1)/2 = sumOfSequence - // sum(t^2) for t from 1 to dt = dt*(dt+1)*(2dt + 1)/6 = sumOfSquares - // so result will be: (k-p)^2 * sumOfSquares + 2(k-p)(b-q)*sumOfSequence + dt*(b-q)^2 - unchecked { - int256 k = (tick1 - tick0) - (avgTick1 - avgTick0); // (k - p)*dt - int256 b = (tick0 - avgTick0) * dt; // (b - q)*dt - int256 sumOfSequence = dt * (dt + 1); // sumOfSequence * 2 - int256 sumOfSquares = sumOfSequence * (2 * dt + 1); // sumOfSquares * 6 - volatility = uint256((k ** 2 * sumOfSquares + 6 * b * k * sumOfSequence + 6 * dt * b ** 2) / (6 * dt ** 2)); - } - } - - /// @notice Calculates average tick for WINDOW seconds at the moment of `time` - /// @dev Guaranteed that the result is within the bounds of int24 - /// @return avgTick The average tick - /// @return windowStartIndex The index of closest timepoint <= WINDOW seconds ago - function _getAverageTickCasted( - Timepoint[UINT16_MODULO] storage self, - uint32 time, - int24 tick, - uint16 lastIndex, - uint16 oldestIndex, - uint32 lastTimestamp, - int56 lastTickCumulative - ) internal view returns (int24 avgTick, uint16 windowStartIndex) { - (int256 _avgTick, uint256 _windowStartIndex) = _getAverageTick(self, time, tick, lastIndex, oldestIndex, lastTimestamp, lastTickCumulative); - unchecked { - (avgTick, windowStartIndex) = (int24(_avgTick), uint16(_windowStartIndex)); // overflow in uint16(_windowStartIndex) is desired - } - } - - /// @notice Calculates average tick for WINDOW seconds at the moment of `currentTime` - /// @dev Guaranteed that the result is within the bounds of int24, but result is not casted - /// @return avgTick int256 for fuzzy tests - /// @return windowStartIndex The index of closest timepoint <= WINDOW seconds ago - function _getAverageTick( - Timepoint[UINT16_MODULO] storage self, - uint32 currentTime, - int24 tick, - uint16 lastIndex, - uint16 oldestIndex, - uint32 lastTimestamp, - int56 lastTickCumulative - ) internal view returns (int256 avgTick, uint256 windowStartIndex) { - (uint32 oldestTimestamp, int56 oldestTickCumulative) = (self[oldestIndex].blockTimestamp, self[oldestIndex].tickCumulative); - - unchecked { - int56 currentTickCumulative = lastTickCumulative + int56(tick) * int56(uint56(currentTime - lastTimestamp)); // update with new data - if (!_lteConsideringOverflow(oldestTimestamp, currentTime - WINDOW, currentTime)) { - // if oldest is newer than WINDOW ago - if (currentTime == oldestTimestamp) return (tick, oldestIndex); - return ((currentTickCumulative - oldestTickCumulative) / int56(uint56(currentTime - oldestTimestamp)), oldestIndex); - } - - if (_lteConsideringOverflow(lastTimestamp, currentTime - WINDOW, currentTime)) { - // if last timepoint is older or equal than WINDOW ago - return (tick, lastIndex); - } else { - int56 tickCumulativeAtStart; - (tickCumulativeAtStart, windowStartIndex) = _getTickCumulativeAt(self, currentTime, WINDOW, tick, lastIndex, oldestIndex); - - // current-WINDOW last current - // _________*____________*_______*_ - // |||||||||||||||||||||| - avgTick = (currentTickCumulative - tickCumulativeAtStart) / int56(uint56(WINDOW)); - } - } - } - - /// @notice comparator for 32-bit timestamps - /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to currentTime - /// @param a A comparison timestamp from which to determine the relative position of `currentTime` - /// @param b From which to determine the relative position of `currentTime` - /// @param currentTime A timestamp truncated to 32 bits - /// @return res Whether `a` is chronologically <= `b` - function _lteConsideringOverflow(uint32 a, uint32 b, uint32 currentTime) internal pure returns (bool res) { - res = a > currentTime; - if (res == b > currentTime) res = a <= b; // if both are on the same side - } - - /// @notice Calculates cumulative volatility at the moment of `time` - `secondsAgo` - /// @dev More optimal than via `getSingleTimepoint` - /// @return volatilityCumulative The cumulative volatility - function _getVolatilityCumulativeAt( - Timepoint[UINT16_MODULO] storage self, - uint32 time, - uint32 secondsAgo, - int24 tick, - uint16 lastIndex, - uint16 oldestIndex - ) internal view returns (uint88 volatilityCumulative) { - unchecked { - uint32 target = time - secondsAgo; - (Timepoint storage beforeOrAt, Timepoint storage atOrAfter, bool samePoint, ) = _getTimepointsAt(self, time, target, lastIndex, oldestIndex); - - (uint32 timestampBefore, uint88 volatilityCumulativeBefore) = (beforeOrAt.blockTimestamp, beforeOrAt.volatilityCumulative); - if (target == timestampBefore) return volatilityCumulativeBefore; // we're at the left boundary - if (samePoint) { - // since target != beforeOrAt.blockTimestamp, `samePoint` means that target is newer than last timepoint - (int24 avgTick, ) = _getAverageTickCasted(self, target, tick, lastIndex, oldestIndex, timestampBefore, beforeOrAt.tickCumulative); - - return (volatilityCumulativeBefore + - uint88(_volatilityOnRange(int256(uint256(target - timestampBefore)), tick, tick, beforeOrAt.averageTick, avgTick))); - } - - (uint32 timestampAfter, uint88 volatilityCumulativeAfter) = (atOrAfter.blockTimestamp, atOrAfter.volatilityCumulative); - if (target == timestampAfter) return volatilityCumulativeAfter; // we're at the right boundary - - // we're in the middle - (uint32 timepointTimeDelta, uint32 targetDelta) = (timestampAfter - timestampBefore, target - timestampBefore); - return volatilityCumulativeBefore + ((volatilityCumulativeAfter - volatilityCumulativeBefore) / timepointTimeDelta) * targetDelta; - } - } - - /// @notice Calculates cumulative tick at the moment of `time` - `secondsAgo` - /// @dev More optimal than via `getSingleTimepoint` - /// @return tickCumulative The cumulative tick - /// @return indexBeforeOrAt The index of closest timepoint before or at the moment of `time` - `secondsAgo` - function _getTickCumulativeAt( - Timepoint[UINT16_MODULO] storage self, - uint32 time, - uint32 secondsAgo, - int24 tick, - uint16 lastIndex, - uint16 oldestIndex - ) internal view returns (int56 tickCumulative, uint256 indexBeforeOrAt) { - unchecked { - uint32 target = time - secondsAgo; - (Timepoint storage beforeOrAt, Timepoint storage atOrAfter, bool samePoint, uint256 _indexBeforeOrAt) = _getTimepointsAt( - self, - time, - target, - lastIndex, - oldestIndex - ); - - (uint32 timestampBefore, int56 tickCumulativeBefore) = (beforeOrAt.blockTimestamp, beforeOrAt.tickCumulative); - if (target == timestampBefore) return (tickCumulativeBefore, _indexBeforeOrAt); // we're at the left boundary - // since target != timestampBefore, `samePoint` means that target is newer than last timepoint - if (samePoint) return ((tickCumulativeBefore + int56(tick) * int56(uint56(target - timestampBefore))), _indexBeforeOrAt); // if target is newer than last timepoint - - (uint32 timestampAfter, int56 tickCumulativeAfter) = (atOrAfter.blockTimestamp, atOrAfter.tickCumulative); - if (target == timestampAfter) return (tickCumulativeAfter, uint16(_indexBeforeOrAt + 1)); // we're at the right boundary - - // we're in the middle - (uint32 timepointTimeDelta, uint32 targetDelta) = (timestampAfter - timestampBefore, target - timestampBefore); - return ( - tickCumulativeBefore + ((tickCumulativeAfter - tickCumulativeBefore) / int56(uint56(timepointTimeDelta))) * int56(uint56(targetDelta)), - _indexBeforeOrAt - ); - } - } - - /// @notice Returns closest timepoint or timepoints to the moment of `target` - /// @return beforeOrAt The timepoint recorded before, or at, the target - /// @return atOrAfter The timepoint recorded at, or after, the target - /// @return samePoint Are `beforeOrAt` and `atOrAfter` the same or not - /// @return indexBeforeOrAt The index of closest timepoint before or at the moment of `target` - function _getTimepointsAt( - Timepoint[UINT16_MODULO] storage self, - uint32 currentTime, - uint32 target, - uint16 lastIndex, - uint16 oldestIndex - ) private view returns (Timepoint storage beforeOrAt, Timepoint storage atOrAfter, bool samePoint, uint256 indexBeforeOrAt) { - Timepoint storage lastTimepoint = self[lastIndex]; - uint32 lastTimepointTimestamp = lastTimepoint.blockTimestamp; - uint16 windowStartIndex = lastTimepoint.windowStartIndex; - - // if target is newer than last timepoint - if (target == currentTime || _lteConsideringOverflow(lastTimepointTimestamp, target, currentTime)) { - return (lastTimepoint, lastTimepoint, true, lastIndex); - } - - bool useHeuristic; - unchecked { - if (lastTimepointTimestamp - target <= WINDOW) { - // We can limit the scope of the search. It is safe because when the array overflows, - // `windowsStartIndex` cannot point to the overwritten timepoint (check at `write(...)`) - oldestIndex = windowStartIndex; - useHeuristic = target == currentTime - WINDOW; // heuristic will optimize search for timepoints close to `currentTime - WINDOW` - } - uint32 oldestTimestamp = self[oldestIndex].blockTimestamp; - - if (!_lteConsideringOverflow(oldestTimestamp, target, currentTime)) revert targetIsTooOld(); - if (oldestTimestamp == target) return (self[oldestIndex], self[oldestIndex], true, oldestIndex); - - // no need to search if we already know the answer - if (lastIndex == oldestIndex + 1) return (self[oldestIndex], lastTimepoint, false, oldestIndex); - } - - (beforeOrAt, atOrAfter, indexBeforeOrAt) = _binarySearch(self, currentTime, target, lastIndex, oldestIndex, useHeuristic); - return (beforeOrAt, atOrAfter, false, indexBeforeOrAt); - } - - /// @notice Fetches the timepoints beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied. - /// The result may be the same timepoint, or adjacent timepoints. - /// @dev The answer must be older than the most recent timepoint and younger, or the same age as, the oldest timepoint - /// @param self The stored timepoints array - /// @param currentTime The current block.timestamp - /// @param target The timestamp at which the timepoint should be - /// @param upperIndex The index of the upper border of search range - /// @param lowerIndex The index of the lower border of search range - /// @param withHeuristic Use heuristic for first guess or not (optimize for targets close to `lowerIndex`) - /// @return beforeOrAt The timepoint recorded before, or at, the target - /// @return atOrAfter The timepoint recorded at, or after, the target - function _binarySearch( - Timepoint[UINT16_MODULO] storage self, - uint32 currentTime, - uint32 target, - uint16 upperIndex, - uint16 lowerIndex, - bool withHeuristic - ) private view returns (Timepoint storage beforeOrAt, Timepoint storage atOrAfter, uint256 indexBeforeOrAt) { - unchecked { - uint256 left = lowerIndex; // oldest timepoint - uint256 right = upperIndex < lowerIndex ? upperIndex + UINT16_MODULO : upperIndex; // newest timepoint considering one index overflow - (beforeOrAt, atOrAfter, indexBeforeOrAt) = _binarySearchInternal(self, currentTime, target, left, right, withHeuristic); - } - } - - function _binarySearchInternal( - Timepoint[UINT16_MODULO] storage self, - uint32 currentTime, - uint32 target, - uint256 left, - uint256 right, - bool withHeuristic - ) private view returns (Timepoint storage beforeOrAt, Timepoint storage atOrAfter, uint256 indexBeforeOrAt) { - unchecked { - if (withHeuristic && right - left > 2) { - indexBeforeOrAt = left + 1; // heuristic for first guess - } else { - indexBeforeOrAt = (left + right) >> 1; // "middle" point between the boundaries - } - beforeOrAt = self[uint16(indexBeforeOrAt)]; // checking the "middle" point between the boundaries - atOrAfter = beforeOrAt; // to suppress compiler warning; will be overridden - bool firstIteration = true; - do { - (bool initializedBefore, uint32 timestampBefore) = (beforeOrAt.initialized, beforeOrAt.blockTimestamp); - if (initializedBefore) { - if (_lteConsideringOverflow(timestampBefore, target, currentTime)) { - // is current point before or at `target`? - atOrAfter = self[uint16(indexBeforeOrAt + 1)]; // checking the next point after "middle" - (bool initializedAfter, uint32 timestampAfter) = (atOrAfter.initialized, atOrAfter.blockTimestamp); - if (initializedAfter) { - if (_lteConsideringOverflow(target, timestampAfter, currentTime)) { - // is the "next" point after or at `target`? - return (beforeOrAt, atOrAfter, indexBeforeOrAt); // the only fully correct way to finish - } - left = indexBeforeOrAt + 1; // "next" point is before the `target`, so looking in the right half - } else { - // beforeOrAt is initialized and <= target, and next timepoint is uninitialized - // should be impossible if initial boundaries and `target` are correct - return (beforeOrAt, beforeOrAt, indexBeforeOrAt); - } - } else { - right = indexBeforeOrAt - 1; // current point is after the `target`, so looking in the left half - } - } else { - // we've landed on an uninitialized timepoint, keep searching higher - // should be impossible if initial boundaries and `target` are correct - left = indexBeforeOrAt + 1; - } - // use heuristic if looking in the right half after first iteration - bool useHeuristic = firstIteration && withHeuristic && left == indexBeforeOrAt + 1; - if (useHeuristic && right - left > 16) { - indexBeforeOrAt = left + 8; - } else { - indexBeforeOrAt = (left + right) >> 1; // calculating the new "middle" point index after updating the bounds - } - beforeOrAt = self[uint16(indexBeforeOrAt)]; // update the "middle" point pointer - firstIteration = false; - } while (true); - } - } -} diff --git a/src/plugin/contracts/libraries/integration/OracleLibrary.sol b/src/plugin/contracts/libraries/integration/OracleLibrary.sol deleted file mode 100644 index 6d334a7bc..000000000 --- a/src/plugin/contracts/libraries/integration/OracleLibrary.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.8.4; - -import '@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; -import '@cryptoalgebra/integral-periphery/contracts/libraries/PoolAddress.sol'; - -import '../../interfaces/plugins/IVolatilityOracle.sol'; - -/// @title Oracle library -/// @notice Provides functions to integrate with Algebra pool TWAP VolatilityOracle -library OracleLibrary { - /// @notice Fetches time-weighted average tick using Algebra VolatilityOracle - /// @param oracleAddress The address of oracle - /// @param period Number of seconds in the past to start calculating time-weighted average - /// @return timeWeightedAverageTick The time-weighted average tick from (block.timestamp - period) to block.timestamp - function consult(address oracleAddress, uint32 period) internal view returns (int24 timeWeightedAverageTick) { - require(period != 0, 'Period is zero'); - - uint32[] memory secondAgos = new uint32[](2); - secondAgos[0] = period; - secondAgos[1] = 0; - - IVolatilityOracle oracle = IVolatilityOracle(oracleAddress); - (int56[] memory tickCumulatives, ) = oracle.getTimepoints(secondAgos); - int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0]; - - timeWeightedAverageTick = int24(tickCumulativesDelta / int56(uint56(period))); - - // Always round to negative infinity - if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(period)) != 0)) timeWeightedAverageTick--; - } - - /// @notice Given a tick and a token amount, calculates the amount of token received in exchange - /// @param tick Tick value used to calculate the quote - /// @param baseAmount Amount of token to be converted - /// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination - /// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination - /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken - function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) internal pure returns (uint256 quoteAmount) { - uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick); - - // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself - if (sqrtRatioX96 <= type(uint128).max) { - uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96; - quoteAmount = baseToken < quoteToken ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192) : FullMath.mulDiv(1 << 192, baseAmount, ratioX192); - } else { - uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64); - quoteAmount = baseToken < quoteToken ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128) : FullMath.mulDiv(1 << 128, baseAmount, ratioX128); - } - } - - /// @notice Fetches metadata of last available record (most recent) in oracle - /// @param oracleAddress The address of oracle - /// @return index The index of last available record (most recent) in oracle - /// @return timestamp The timestamp of last available record (most recent) in oracle, truncated to uint32 - function lastTimepointMetadata(address oracleAddress) internal view returns (uint16 index, uint32 timestamp) { - index = latestIndex(oracleAddress); - timestamp = IVolatilityOracle(oracleAddress).lastTimepointTimestamp(); - } - - /// @notice Fetches metadata of oldest available record in oracle - /// @param oracleAddress The address of oracle - /// @return index The index of oldest available record in oracle - /// @return timestamp The timestamp of oldest available record in oracle, truncated to uint32 - function oldestTimepointMetadata(address oracleAddress) internal view returns (uint16 index, uint32 timestamp) { - uint16 lastIndex = latestIndex(oracleAddress); - bool initialized; - unchecked { - // overflow is desired - index = lastIndex + 1; - (initialized, timestamp) = timepointMetadata(oracleAddress, index); - } - if (initialized) return (index, timestamp); - - (, timestamp) = timepointMetadata(oracleAddress, 0); - return (0, timestamp); - } - - /// @notice Gets information about whether the oracle has been initialized - function isInitialized(address oracleAddress) internal view returns (bool result) { - (result, ) = timepointMetadata(oracleAddress, 0); - return result; - } - - /// @notice Fetches the index of last available record (most recent) in oracle - function latestIndex(address oracle) internal view returns (uint16) { - return (IVolatilityOracle(oracle).timepointIndex()); - } - - /// @notice Fetches the metadata of record in oracle - /// @param oracleAddress The address of oracle - /// @param index The index of record in oracle - /// @return initialized Whether or not the timepoint is initialized - /// @return timestamp The timestamp of timepoint - function timepointMetadata(address oracleAddress, uint16 index) internal view returns (bool initialized, uint32 timestamp) { - (initialized, timestamp, , , , , ) = IVolatilityOracle(oracleAddress).timepoints(index); - } - - /// @notice Checks if the oracle is currently connected to the pool - /// @param oracleAddress The address of oracle - /// @param oracleAddress The address of the pool - /// @return connected Whether or not the oracle is connected - function isOracleConnectedToPool(address oracleAddress, address poolAddress) internal view returns (bool connected) { - IAlgebraPool pool = IAlgebraPool(poolAddress); - if (oracleAddress == pool.plugin()) { - (, , , uint8 pluginConfig, , ) = pool.globalState(); - connected = Plugins.hasFlag(pluginConfig, Plugins.BEFORE_SWAP_FLAG); - } - } -} diff --git a/src/plugin/contracts/plugins/DynamicFeePlugin.sol b/src/plugin/contracts/plugins/DynamicFeePlugin.sol deleted file mode 100644 index ef98e3e5d..000000000 --- a/src/plugin/contracts/plugins/DynamicFeePlugin.sol +++ /dev/null @@ -1,63 +0,0 @@ -// 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 '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; - -import '../interfaces/IBasePluginV1Factory.sol'; -import '../interfaces/plugins/IDynamicFeeManager.sol'; - -import '../libraries/AdaptiveFee.sol'; -import '../types/AlgebraFeeConfigurationU144.sol'; -import '../base/AlgebraBasePlugin.sol'; - -/// @title Algebra Integral 1.2.2 default plugin -/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages -abstract contract DynamicFeePlugin is AlgebraBasePlugin, IDynamicFeeManager { - using Plugins for uint8; - using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfiguration; - - uint8 private constant defaultPluginConfig = uint8(Plugins.BEFORE_SWAP_FLAG | Plugins.DYNAMIC_FEE); - - /// @dev AlgebraFeeConfiguration struct packed in uint144 - AlgebraFeeConfigurationU144 internal _feeConfig; - - constructor(AlgebraFeeConfiguration memory _config) { - AdaptiveFee.validateFeeConfiguration(_config); - - _feeConfig = _config.pack(); // pack struct to uint144 and write in storage - } - - /// @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(); - } - - // ###### Fee manager ###### - - /// @inheritdoc IDynamicFeeManager - function changeFeeConfiguration(AlgebraFeeConfiguration calldata _config) external override { - _authorize(); - AdaptiveFee.validateFeeConfiguration(_config); - - _feeConfig = _config.pack(); // pack struct to uint144 and write in storage - emit FeeConfiguration(_config); - } - - function _getCurrentFee(uint88 volatilityAverage) internal view returns (uint16 fee) { - AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; - if (feeConfig_.alpha1() | feeConfig_.alpha2() == 0) return feeConfig_.baseFee(); - - return AdaptiveFee.getFee(volatilityAverage, feeConfig_); - } -} diff --git a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol deleted file mode 100644 index cc22a2ee3..000000000 --- a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol +++ /dev/null @@ -1,85 +0,0 @@ -// 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/IAlgebraPool.sol'; - -import '../interfaces/IBasePluginV1Factory.sol'; -import '../interfaces/IAlgebraVirtualPool.sol'; -import '../interfaces/plugins/IFarmingPlugin.sol'; - -import '../base/AlgebraBasePlugin.sol'; - -/// @title Algebra Integral 1.2.2 default plugin -/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages -abstract contract FarmingProxyPlugin is AlgebraBasePlugin, IFarmingPlugin { - using Plugins for uint8; - - uint8 private constant defaultPluginConfig = uint8(Plugins.AFTER_SWAP_FLAG); - - /// @inheritdoc IFarmingPlugin - address public override incentive; - - /// @dev the address which connected the last incentive. Needed so that he can disconnect it - address private _lastIncentiveOwner; - - /// @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) { - _enablePluginFlags(defaultPluginConfig); - } - } - - /// @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; - } - - function _updateVirtualPoolTick(bool zeroToOne) internal { - address _incentive = incentive; - if (_incentive != address(0)) { - (, int24 tick, , ) = _getPoolState(); - IAlgebraVirtualPool(_incentive).crossTo(tick, zeroToOne); - } else { - _disablePluginFlags(defaultPluginConfig); // should not be called, reset config - } - } - - function getPool() external view override returns (address) { - return pool; - } -} diff --git a/src/plugin/contracts/plugins/SlidingFeePlugin.sol b/src/plugin/contracts/plugins/SlidingFeePlugin.sol deleted file mode 100644 index f1443b828..000000000 --- a/src/plugin/contracts/plugins/SlidingFeePlugin.sol +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.20; - -import {IAlgebraFactory} from '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol'; -import {IAlgebraPool} from '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; -import {Timestamp} from '@cryptoalgebra/integral-core/contracts/base/common/Timestamp.sol'; -import {TickMath} from '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol'; -import {FullMath} from '@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol'; - -import {ISlidingFeePlugin} from '../interfaces/plugins/ISlidingFeePlugin.sol'; -import {AlgebraBasePlugin} from '../base/AlgebraBasePlugin.sol'; - -abstract contract SlidingFeePlugin is AlgebraBasePlugin, ISlidingFeePlugin { - struct FeeFactors { - uint128 zeroToOneFeeFactor; - uint128 oneToZeroFeeFactor; - } - - int16 internal constant FACTOR_DENOMINATOR = 1000; - uint64 internal constant FEE_FACTOR_SHIFT = 96; - - FeeFactors public s_feeFactors; - - uint16 public s_priceChangeFactor = 1000; - uint16 public s_baseFee = 3000; - - constructor(uint16 _baseFee) { - FeeFactors memory feeFactors = FeeFactors(uint128(1 << FEE_FACTOR_SHIFT), uint128(1 << FEE_FACTOR_SHIFT)); - - s_feeFactors = feeFactors; - s_baseFee = _baseFee; - } - - function _getFeeAndUpdateFactors(bool zeroToOne, int24 currenTick, int24 lastTick) internal returns (uint16) { - FeeFactors memory currentFeeFactors; - - uint16 priceChangeFactor = s_priceChangeFactor; - uint16 baseFee = s_baseFee; - - if (currenTick != lastTick) { - currentFeeFactors = _calculateFeeFactors(currenTick, lastTick, priceChangeFactor); - - s_feeFactors = currentFeeFactors; - } else { - currentFeeFactors = s_feeFactors; - } - - uint256 adjustedFee = zeroToOne - ? (uint256(baseFee) * currentFeeFactors.zeroToOneFeeFactor) >> FEE_FACTOR_SHIFT - : (uint256(baseFee) * currentFeeFactors.oneToZeroFeeFactor) >> FEE_FACTOR_SHIFT; - - if (adjustedFee > type(uint16).max) { - adjustedFee = type(uint16).max; - } else if (adjustedFee == 0) { - adjustedFee = 1; - } - return uint16(adjustedFee); - } - - function setPriceChangeFactor(uint16 newPriceChangeFactor) external override { - _authorize(); - - s_priceChangeFactor = newPriceChangeFactor; - - emit PriceChangeFactor(newPriceChangeFactor); - } - - function setBaseFee(uint16 newBaseFee) external override { - _authorize(); - - s_baseFee = newBaseFee; - emit BaseFee(newBaseFee); - } - - function _calculateFeeFactors(int24 currentTick, int24 lastTick, uint16 priceChangeFactor) internal view returns (FeeFactors memory feeFactors) { - int256 tickDelta = int256(currentTick) - int256(lastTick); - if (tickDelta > TickMath.MAX_TICK) { - tickDelta = TickMath.MAX_TICK; - } else if (tickDelta < TickMath.MIN_TICK) { - tickDelta = TickMath.MIN_TICK; - } - uint256 sqrtPriceDelta = uint256(TickMath.getSqrtRatioAtTick(int24(tickDelta))); - - // price change is positive after oneToZero prevalence - int256 priceChangeRatio = int256(FullMath.mulDiv(sqrtPriceDelta, sqrtPriceDelta, 2 ** 96)) - int256(1 << FEE_FACTOR_SHIFT); // (currentPrice - lastPrice) / lastPrice - int256 feeFactorImpact = (priceChangeRatio * int256(uint256(priceChangeFactor))) / FACTOR_DENOMINATOR; - - feeFactors = s_feeFactors; - - // if there were zeroToOne prevalence in the last price change, - // in result price has increased - // we need to decrease zeroToOneFeeFactor - // and vice versa - int256 newZeroToOneFeeFactor = int128(feeFactors.zeroToOneFeeFactor) - feeFactorImpact; - - if (0 < newZeroToOneFeeFactor && newZeroToOneFeeFactor < (int128(2) << FEE_FACTOR_SHIFT)) { - feeFactors = FeeFactors(uint128(int128(newZeroToOneFeeFactor)), uint128(int128(feeFactors.oneToZeroFeeFactor) + int128(feeFactorImpact))); - } else if (newZeroToOneFeeFactor <= 0) { - // In this case price has decreased that much so newZeroToOneFeeFactor is less than 0 - // So we set it to the minimal value == 0 - // It means that there were too much oneToZero prevalence and we want to decrease it - // Basically price change is -100% - feeFactors = FeeFactors(0, uint128(2 << FEE_FACTOR_SHIFT)); - } else { - // In this case priceChange is big enough that newZeroToOneFeeFactor is greater than 2 - // So we set it to the maximum value - // It means that there were too much zeroToOne prevalence and we want to decrease it - feeFactors = FeeFactors(uint128(2 << FEE_FACTOR_SHIFT), 0); - } - } -} diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol deleted file mode 100644 index 0b0ff5eed..000000000 --- a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol'; - -import '../interfaces/IBasePlugin.sol'; -import '../interfaces/IBasePluginV1Factory.sol'; -import '../interfaces/plugins/IVolatilityOracle.sol'; - -import '../libraries/VolatilityOracle.sol'; -import '../base/AlgebraBasePlugin.sol'; - -/// @title Algebra Integral 1.2.2 VolatilityOraclePlugin plugin -/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages -abstract contract VolatilityOraclePlugin is AlgebraBasePlugin, IVolatilityOracle { - using Plugins for uint8; - - uint256 internal constant UINT16_MODULO = 65536; - using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; - - uint8 private constant defaultPluginConfig = uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG); - - /// @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; - - /// @inheritdoc IVolatilityOracle - 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'); - _initialize_TWAP(tick); - } - - function _initialize_TWAP(int24 tick) internal { - uint32 time = _blockTimestamp(); - timepoints.initialize(time, tick); - lastTimepointTimestamp = time; - isInitialized = true; - - _enablePluginFlags(defaultPluginConfig); - } - // ###### 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 - } - } - } - - function _writeTimepoint() internal { - // single SLOAD - uint16 _lastIndex = timepointIndex; - uint32 _lastTimepointTimestamp = lastTimepointTimestamp; - - bool _isInitialized = isInitialized; - require(_isInitialized, 'Not initialized'); - - uint32 currentTimestamp = _blockTimestamp(); - if (_lastTimepointTimestamp == currentTimestamp) return; - - (, int24 tick, , ) = _getPoolState(); - (uint16 newLastIndex, ) = timepoints.write(_lastIndex, currentTimestamp, tick); - - timepointIndex = newLastIndex; - lastTimepointTimestamp = currentTimestamp; - } - - function _getAverageVolatilityLast() internal view returns (uint88 volatilityAverage) { - uint32 currentTimestamp = _blockTimestamp(); - (, int24 tick, , ) = _getPoolState(); - - uint16 lastTimepointIndex = timepointIndex; - uint16 oldestIndex = timepoints.getOldestIndex(lastTimepointIndex); - - volatilityAverage = timepoints.getAverageVolatility(currentTimestamp, tick, lastTimepointIndex, oldestIndex); - } - - function _getLastTick() internal view returns (int24 lastTick) { - VolatilityOracle.Timepoint memory lastTimepoint = timepoints[timepointIndex]; - return lastTimepoint.tick; - } -} diff --git a/src/plugin/contracts/test/AdaptiveFeeTest.sol b/src/plugin/contracts/test/AdaptiveFeeTest.sol deleted file mode 100644 index 703ffbec8..000000000 --- a/src/plugin/contracts/test/AdaptiveFeeTest.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../base/AlgebraFeeConfiguration.sol'; -import '../libraries/AdaptiveFee.sol'; - -import '@cryptoalgebra/integral-core/contracts/libraries/Constants.sol'; - -contract AdaptiveFeeTest { - using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfiguration; - - AlgebraFeeConfiguration public feeConfig; - - constructor() { - feeConfig = AdaptiveFee.initialFeeConfiguration(); - } - - function getFee(uint88 volatility) external view returns (uint256 fee) { - return AdaptiveFee.getFee(volatility, feeConfig.pack()); - } - - function getGasCostOfGetFee(uint88 volatility) external view returns (uint256) { - AlgebraFeeConfigurationU144 _packed = feeConfig.pack(); - unchecked { - uint256 gasBefore = gasleft(); - AdaptiveFee.getFee(volatility, _packed); - return gasBefore - gasleft(); - } - } - - function packAndUnpackFeeConfig(AlgebraFeeConfiguration calldata config) external pure returns (AlgebraFeeConfiguration memory unpacked) { - AlgebraFeeConfigurationU144 _packed = AlgebraFeeConfigurationU144Lib.pack(config); - unpacked.alpha1 = _packed.alpha1(); - unpacked.alpha2 = _packed.alpha2(); - unpacked.beta1 = _packed.beta1(); - unpacked.beta2 = _packed.beta2(); - unpacked.gamma1 = _packed.gamma1(); - unpacked.gamma2 = _packed.gamma2(); - unpacked.baseFee = _packed.baseFee(); - } -} diff --git a/src/plugin/contracts/test/MockFactory.sol b/src/plugin/contracts/test/MockFactory.sol deleted file mode 100644 index f3a28b2c6..000000000 --- a/src/plugin/contracts/test/MockFactory.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -/// @title Mock of Algebra factory for plugins testing -contract MockFactory { - bytes32 public constant POOLS_ADMINISTRATOR_ROLE = keccak256('POOLS_ADMINISTRATOR'); - - address public owner; - - mapping(address => mapping(bytes32 => bool)) public hasRole; - - mapping(address => mapping(address => address)) public poolByPair; - - constructor() { - owner = msg.sender; - } - - function hasRoleOrOwner(bytes32 role, address account) public view returns (bool) { - return (owner == account || hasRole[account][role]); - } - - function grantRole(bytes32 role, address account) external { - hasRole[account][role] = true; - } - - function revokeRole(bytes32 role, address account) external { - hasRole[account][role] = false; - } - - function stubPool(address token0, address token1, address pool) public { - poolByPair[token0][token1] = pool; - poolByPair[token1][token0] = pool; - } -} diff --git a/src/plugin/contracts/test/MockObservable.sol b/src/plugin/contracts/test/MockObservable.sol deleted file mode 100644 index 75af06c26..000000000 --- a/src/plugin/contracts/test/MockObservable.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../libraries/VolatilityOracle.sol'; -import '../interfaces/plugins/IVolatilityOracle.sol'; - -contract MockVolatilityOracle is IVolatilityOracle { - VolatilityOracle.Timepoint[3] public override timepoints; - - uint16 private _lastTimepointIndex = 1; - - bool public isInitialized; - - constructor(uint32[] memory secondsAgos, int56[] memory tickCumulatives) { - require(secondsAgos.length == 2 && tickCumulatives.length == 2, 'Invalid test case size'); - - timepoints[0].initialized = true; - timepoints[0].blockTimestamp = secondsAgos[0]; - timepoints[0].tickCumulative = tickCumulatives[0]; - - timepoints[1].initialized = true; - timepoints[1].blockTimestamp = secondsAgos[1]; - timepoints[1].tickCumulative = tickCumulatives[1]; - - isInitialized = true; - } - - function getTimepoints( - uint32[] calldata secondsAgos - ) external view override returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { - require(secondsAgos[0] == timepoints[0].blockTimestamp && secondsAgos[1] == timepoints[1].blockTimestamp, 'Invalid test case'); - - int56[] memory _tickCumulatives = new int56[](2); - uint88[] memory _volatilityCumulatives = new uint88[](2); - - _tickCumulatives[0] = timepoints[0].tickCumulative; - _volatilityCumulatives[0] = timepoints[0].volatilityCumulative; - - _tickCumulatives[1] = timepoints[1].tickCumulative; - _volatilityCumulatives[1] = timepoints[1].volatilityCumulative; - - return (_tickCumulatives, _volatilityCumulatives); - } - - function timepointIndex() external view override returns (uint16) { - return _lastTimepointIndex; - } - - function setLastIndex(uint16 newValue) external { - _lastTimepointIndex = newValue; - } - - function setTimepoint(uint16 index, bool initialized, uint32 timestamp, int56 tickCumulative, uint88 volatilityCumulative) external { - timepoints[index].initialized = initialized; - timepoints[index].blockTimestamp = timestamp; - timepoints[index].tickCumulative = tickCumulative; - timepoints[index].volatilityCumulative = volatilityCumulative; - } - - function lastTimepointTimestamp() external pure override returns (uint32) { - return 101; - } - - function getSingleTimepoint(uint32 secondsAgo) external view override returns (int56 tickCumulative, uint88 volatilityCumulative) {} - - function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external override {} - - function initialize() external { - - } -} diff --git a/src/plugin/contracts/test/MockPool.sol b/src/plugin/contracts/test/MockPool.sol deleted file mode 100644 index 2528518c4..000000000 --- a/src/plugin/contracts/test/MockPool.sol +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; -pragma abicoder v1; - -import '@cryptoalgebra/integral-core/contracts/libraries/TickManagement.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/Constants.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol'; -import '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol'; - -import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolActions.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolState.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolPermissionedActions.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolErrors.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol'; - -/// @title Mock of Algebra concentrated liquidity pool for plugins testing -contract MockPool is IAlgebraPoolActions, IAlgebraPoolPermissionedActions, IAlgebraPoolState { - struct GlobalState { - uint160 price; // The square root of the current price in Q64.96 format - int24 tick; // The current tick - uint16 fee; // The current fee in hundredths of a bip, i.e. 1e-6 - uint8 pluginConfig; - uint16 communityFee; // The community fee represented as a percent of all collected fee in thousandths (1e-3) - bool unlocked; // True if the contract is unlocked, otherwise - false - } - - /// @inheritdoc IAlgebraPoolState - uint256 public override totalFeeGrowth0Token; - /// @inheritdoc IAlgebraPoolState - uint256 public override totalFeeGrowth1Token; - /// @inheritdoc IAlgebraPoolState - GlobalState public override globalState; - - /// @inheritdoc IAlgebraPoolState - int24 public override nextTickGlobal; - /// @inheritdoc IAlgebraPoolState - int24 public override prevTickGlobal; - - /// @inheritdoc IAlgebraPoolState - uint128 public override liquidity; - /// @inheritdoc IAlgebraPoolState - int24 public override tickSpacing; - /// @inheritdoc IAlgebraPoolState - uint32 public override lastFeeTransferTimestamp; - - /// @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 - - /// @inheritdoc IAlgebraPoolState - address public override plugin; - - address public override communityVault; - - /// @inheritdoc IAlgebraPoolState - mapping(int24 => TickManagement.Tick) public override ticks; - - /// @inheritdoc IAlgebraPoolState - mapping(int16 => uint256) public override tickTable; - - 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; - - address owner; - uint24 public overrideFee; - uint24 public pluginFee; - - /// @inheritdoc IAlgebraPoolState - function getCommunityFeePending() external pure override returns (uint128, uint128) { - revert('not implemented'); - } - - /// @inheritdoc IAlgebraPoolState - function getPluginFeePending() external pure override returns (uint128, uint128) { - revert('not implemented'); - } - - /// @inheritdoc IAlgebraPoolState - function fee() external pure returns (uint16) { - revert('not implemented'); - } - - /// @inheritdoc IAlgebraPoolState - function safelyGetStateOfAMM() external pure override returns (uint160, int24, uint16, uint8, uint128, int24, int24) { - revert('not implemented'); - } - - constructor() { - globalState.fee = Constants.INIT_DEFAULT_FEE; - globalState.unlocked = true; - owner = msg.sender; - } - - /// @inheritdoc IAlgebraPoolState - function getReserves() external pure override returns (uint128, uint128) { - revert('not implemented'); - } - - /// @inheritdoc IAlgebraPoolState - function isUnlocked() external view override returns (bool unlocked) { - return globalState.unlocked; - } - - /// @inheritdoc IAlgebraPoolActions - function initialize(uint160 initialPrice) external override { - int24 tick = TickMath.getTickAtSqrtRatio(initialPrice); // getTickAtSqrtRatio checks validity of initialPrice inside - - if (plugin != address(0)) { - IAlgebraPlugin(plugin).beforeInitialize(msg.sender, initialPrice); - } - - tickSpacing = 60; - - uint8 pluginConfig = globalState.pluginConfig; - - globalState.price = initialPrice; - globalState.tick = tick; - - if (pluginConfig & Plugins.AFTER_INIT_FLAG != 0) { - IAlgebraPlugin(plugin).afterInitialize(msg.sender, initialPrice, tick); - } - } - - /// @inheritdoc IAlgebraPoolActions - function mint( - address, - address recipient, - int24 bottomTick, - int24 topTick, - uint128 liquidityDesired, - bytes calldata data - ) external override returns (uint256, uint256, uint128) { - if (globalState.pluginConfig & Plugins.BEFORE_POSITION_MODIFY_FLAG != 0) { - IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, recipient, bottomTick, topTick, int128(liquidityDesired), data); - } - - if (globalState.pluginConfig & Plugins.AFTER_POSITION_MODIFY_FLAG != 0) { - IAlgebraPlugin(plugin).afterModifyPosition(msg.sender, recipient, bottomTick, topTick, int128(liquidityDesired), 0, 0, data); - } - return (0, 0, 0); - } - - /// @inheritdoc IAlgebraPoolActions - function burn(int24 bottomTick, int24 topTick, uint128 liquidityDesired, bytes calldata data) external override returns (uint256, uint256) { - if (globalState.pluginConfig & Plugins.BEFORE_POSITION_MODIFY_FLAG != 0) { - IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, msg.sender, bottomTick, topTick, -int128(liquidityDesired), data); - } - - if (globalState.pluginConfig & Plugins.AFTER_POSITION_MODIFY_FLAG != 0) { - IAlgebraPlugin(plugin).afterModifyPosition(msg.sender, msg.sender, bottomTick, topTick, -int128(liquidityDesired), 0, 0, data); - } - return (0, 0); - } - - /// @inheritdoc IAlgebraPoolActions - function collect(address, int24, int24, uint128, uint128) external pure override returns (uint128, uint128) { - revert('Not implemented'); - } - - /// @inheritdoc IAlgebraPoolActions - function swap(address, bool, int256, uint160, bytes calldata) external pure override returns (int256, int256) { - revert('Not implemented'); - } - - function swapToTick(int24 targetTick) external { - IAlgebraPlugin _plugin = IAlgebraPlugin(plugin); - if (globalState.pluginConfig & Plugins.BEFORE_SWAP_FLAG != 0) { - (, overrideFee, pluginFee) = _plugin.beforeSwap(msg.sender, msg.sender, true, 0, 0, false, ''); - } - - globalState.price = TickMath.getSqrtRatioAtTick(targetTick); - globalState.tick = targetTick; - - if (globalState.pluginConfig & Plugins.AFTER_SWAP_FLAG != 0) { - _plugin.afterSwap(msg.sender, msg.sender, true, 0, 0, 0, 0, ''); - } - } - - /// @inheritdoc IAlgebraPoolActions - function swapWithPaymentInAdvance(address, address, bool, int256, uint160, bytes calldata) external pure override returns (int256, int256) { - revert('Not implemented'); - } - - /// @inheritdoc IAlgebraPoolActions - function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external override { - uint8 pluginConfig = globalState.pluginConfig; - if (pluginConfig & Plugins.BEFORE_FLASH_FLAG != 0) { - IAlgebraPlugin(plugin).beforeFlash(msg.sender, recipient, amount0, amount1, data); - } - - if (pluginConfig & Plugins.AFTER_FLASH_FLAG != 0) { - IAlgebraPlugin(plugin).afterFlash(msg.sender, recipient, amount0, amount1, 0, 0, data); - } - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function setCommunityFee(uint16 newCommunityFee) external override { - if (newCommunityFee > Constants.MAX_COMMUNITY_FEE || newCommunityFee == globalState.communityFee) - revert IAlgebraPoolErrors.invalidNewCommunityFee(); - globalState.communityFee = newCommunityFee; - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function setTickSpacing(int24 newTickSpacing) external override { - if (newTickSpacing <= 0 || newTickSpacing > Constants.MAX_TICK_SPACING || tickSpacing == newTickSpacing) - revert IAlgebraPoolErrors.invalidNewTickSpacing(); - tickSpacing = newTickSpacing; - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function setPlugin(address newPluginAddress) external override { - require(msg.sender == owner); - plugin = newPluginAddress; - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function setPluginConfig(uint8 newConfig) external override { - require(msg.sender == owner || msg.sender == plugin); - globalState.pluginConfig = newConfig; - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function setFee(uint16 newFee) external override { - require(msg.sender == owner || msg.sender == plugin); - bool isDynamicFeeEnabled = globalState.pluginConfig & uint8(Plugins.DYNAMIC_FEE) != 0; - require(!isDynamicFeeEnabled && msg.sender == owner); - - globalState.fee = newFee; - } - - function setCommunityVault(address newCommunityVault) external override { - communityVault = newCommunityVault; - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function sync() external pure override { - revert('Not implemented'); - } - - /// @inheritdoc IAlgebraPoolPermissionedActions - function skim() external pure override { - revert('Not implemented'); - } -} diff --git a/src/plugin/contracts/test/MockTimeAlgebraBasePluginV1.sol b/src/plugin/contracts/test/MockTimeAlgebraBasePluginV1.sol deleted file mode 100644 index 2781b3679..000000000 --- a/src/plugin/contracts/test/MockTimeAlgebraBasePluginV1.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../AlgebraBasePluginV1.sol'; - -// used for testing time dependent behavior -contract MockTimeAlgebraBasePluginV1 is AlgebraBasePluginV1 { - using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; - - // Monday, October 5, 2020 9:00:00 AM GMT-05:00 - uint256 public time = 1601906400; - - constructor( - address _pool, - address _factory, - address _pluginFactory, - AlgebraFeeConfiguration memory _config - ) AlgebraBasePluginV1(_pool, _factory, _pluginFactory, _config) {} - - function advanceTime(uint256 by) external { - unchecked { - time += by; - } - } - - function _blockTimestamp() internal view override returns (uint32) { - return uint32(time); - } - - struct UpdateParams { - uint32 advanceTimeBy; - int24 tick; - } - - function batchUpdate(UpdateParams[] calldata params) external { - // sload everything - uint16 _index = timepointIndex; - uint32 _time = lastTimepointTimestamp; - int24 _tick; - unchecked { - for (uint256 i; i < params.length; ++i) { - _time += params[i].advanceTimeBy; - _tick = params[i].tick; - (_index, ) = timepoints.write(_index, _time, _tick); - } - } - - // sstore everything - lastTimepointTimestamp = _time; - timepointIndex = _index; - time = _time; - } - - function checkBlockTimestamp() external view returns (bool) { - require(super._blockTimestamp() == uint32(block.timestamp)); - return true; - } - - function getTimepointsWithParams( - uint32 _time, - uint32[] memory secondsAgos, - int24 tick, - uint16 lastIndex - ) external view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { - return timepoints.getTimepoints(_time, secondsAgos, tick, lastIndex); - } - - function getAverageVolatility(uint32 timestamp, int24 tick) public view returns (uint88 volatilityAverage) { - uint16 index = timepointIndex; - uint16 oldestIndex = timepoints.getOldestIndex(index); - return timepoints.getAverageVolatility(timestamp, tick, index, oldestIndex); - } -} diff --git a/src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol b/src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol deleted file mode 100644 index 63995e297..000000000 --- a/src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../AlgebraBasePluginV2.sol'; - -// used for testing time dependent behavior -contract MockTimeAlgebraBasePluginV2 is AlgebraBasePluginV2 { - using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; - - // Monday, October 5, 2020 9:00:00 AM GMT-05:00 - uint256 public time = 1601906400; - - constructor( - address _pool, - address _factory, - address _pluginFactory, - uint16 _baseFee - ) AlgebraBasePluginV2(_pool, _factory, _pluginFactory, _baseFee) {} - - function advanceTime(uint256 by) external { - unchecked { - time += by; - } - } - - function _blockTimestamp() internal view override returns (uint32) { - return uint32(time); - } - - struct UpdateParams { - uint32 advanceTimeBy; - int24 tick; - } - - function batchUpdate(UpdateParams[] calldata params) external { - // sload everything - uint16 _index = timepointIndex; - uint32 _time = lastTimepointTimestamp; - int24 _tick; - unchecked { - for (uint256 i; i < params.length; ++i) { - _time += params[i].advanceTimeBy; - _tick = params[i].tick; - (_index, ) = timepoints.write(_index, _time, _tick); - } - } - - // sstore everything - lastTimepointTimestamp = _time; - timepointIndex = _index; - time = _time; - } - - function checkBlockTimestamp() external view returns (bool) { - require(super._blockTimestamp() == uint32(block.timestamp)); - return true; - } - - function getTimepointsWithParams( - uint32 _time, - uint32[] memory secondsAgos, - int24 tick, - uint16 lastIndex - ) external view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { - return timepoints.getTimepoints(_time, secondsAgos, tick, lastIndex); - } - - function getAverageVolatility(uint32 timestamp, int24 tick) public view returns (uint88 volatilityAverage) { - uint16 index = timepointIndex; - uint16 oldestIndex = timepoints.getOldestIndex(index); - return timepoints.getAverageVolatility(timestamp, tick, index, oldestIndex); - } -} diff --git a/src/plugin/contracts/test/MockTimeDSFactory.sol b/src/plugin/contracts/test/MockTimeDSFactory.sol deleted file mode 100644 index 84965f0da..000000000 --- a/src/plugin/contracts/test/MockTimeDSFactory.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import '../base/AlgebraFeeConfiguration.sol'; -import '../libraries/AdaptiveFee.sol'; - -import './MockTimeAlgebraBasePluginV1.sol'; - -import '../interfaces/IBasePluginV1Factory.sol'; - -import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol'; - -contract MockTimeDSFactory is IBasePluginV1Factory { - /// @inheritdoc IBasePluginV1Factory - bytes32 public constant override ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = keccak256('ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR'); - - address public immutable override algebraFactory; - - /// @dev values of constants for sigmoids in fee calculation formula - AlgebraFeeConfiguration public override defaultFeeConfiguration; - - /// @inheritdoc IBasePluginV1Factory - mapping(address => address) public override pluginByPool; - - /// @inheritdoc IBasePluginV1Factory - address public override farmingAddress; - - constructor(address _algebraFactory) { - algebraFactory = _algebraFactory; - defaultFeeConfiguration = AdaptiveFee.initialFeeConfiguration(); - } - - /// @inheritdoc IAlgebraPluginFactory - function beforeCreatePoolHook(address pool, address, address, address, address, bytes calldata) external override returns (address) { - return _createPlugin(pool); - } - - /// @inheritdoc IAlgebraPluginFactory - function afterCreatePoolHook(address, address, address) external view override { - require(msg.sender == algebraFactory); - } - - 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 setPluginForPool(address pool, address plugin) external { - pluginByPool[pool] = plugin; - } - - function _createPlugin(address pool) internal returns (address) { - MockTimeAlgebraBasePluginV1 volatilityOracle = new MockTimeAlgebraBasePluginV1(pool, algebraFactory, address(this), defaultFeeConfiguration); - pluginByPool[pool] = address(volatilityOracle); - return address(volatilityOracle); - } - - /// @inheritdoc IBasePluginV1Factory - function setDefaultFeeConfiguration(AlgebraFeeConfiguration calldata newConfig) external override { - AdaptiveFee.validateFeeConfiguration(newConfig); - defaultFeeConfiguration = newConfig; - emit DefaultFeeConfiguration(newConfig); - } - - /// @inheritdoc IBasePluginV1Factory - function setFarmingAddress(address newFarmingAddress) external override { - require(farmingAddress != newFarmingAddress); - farmingAddress = newFarmingAddress; - emit FarmingAddress(newFarmingAddress); - } -} diff --git a/src/plugin/contracts/test/MockTimeDSFactoryV2.sol b/src/plugin/contracts/test/MockTimeDSFactoryV2.sol deleted file mode 100644 index 2732bce64..000000000 --- a/src/plugin/contracts/test/MockTimeDSFactoryV2.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import './MockTimeAlgebraBasePluginV2.sol'; - -import '../interfaces/IBasePluginV2Factory.sol'; - -import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol'; - -contract MockTimeDSFactoryV2 is IBasePluginV2Factory { - /// @inheritdoc IBasePluginV2Factory - bytes32 public constant override ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR = keccak256('ALGEBRA_BASE_PLUGIN_FACTORY_ADMINISTRATOR'); - - address public immutable override algebraFactory; - - /// @inheritdoc IBasePluginV2Factory - mapping(address => address) public override pluginByPool; - - /// @inheritdoc IBasePluginV2Factory - address public override farmingAddress; - - /// @inheritdoc IBasePluginV2Factory - uint16 public override defaultBaseFee = 500; - - constructor(address _algebraFactory) { - algebraFactory = _algebraFactory; - } - - /// @inheritdoc IAlgebraPluginFactory - function beforeCreatePoolHook(address pool, address, address, address, address, bytes calldata) external override returns (address) { - return _createPlugin(pool); - } - - /// @inheritdoc IAlgebraPluginFactory - function afterCreatePoolHook(address, address, address) external view override { - require(msg.sender == algebraFactory); - } - - 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 setPluginForPool(address pool, address plugin) external { - pluginByPool[pool] = plugin; - } - - function _createPlugin(address pool) internal returns (address) { - MockTimeAlgebraBasePluginV2 plugin = new MockTimeAlgebraBasePluginV2(pool, algebraFactory, address(this), defaultBaseFee); - pluginByPool[pool] = address(plugin); - return address(plugin); - } - - /// @inheritdoc IBasePluginV2Factory - function setDefaultBaseFee(uint16 newDefaultBaseFee) external override { - require(defaultBaseFee != newDefaultBaseFee); - defaultBaseFee = newDefaultBaseFee; - emit DefaultBaseFee(newDefaultBaseFee); - } - - /// @inheritdoc IBasePluginV2Factory - function setFarmingAddress(address newFarmingAddress) external override { - require(farmingAddress != newFarmingAddress); - farmingAddress = newFarmingAddress; - emit FarmingAddress(newFarmingAddress); - } -} diff --git a/src/plugin/contracts/test/MockTimeVirtualPool.sol b/src/plugin/contracts/test/MockTimeVirtualPool.sol deleted file mode 100644 index 351dc4455..000000000 --- a/src/plugin/contracts/test/MockTimeVirtualPool.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; -pragma abicoder v1; - -import '../interfaces/IAlgebraVirtualPool.sol'; - -contract MockTimeVirtualPool is IAlgebraVirtualPool { - uint32 public timestamp; - int24 public currentTick; - bool private earlyReturn; - - function setEarlyReturn() external { - earlyReturn = true; - } - - function crossTo(int24 nextTick, bool) external override returns (bool) { - if (earlyReturn) return true; - - currentTick = nextTick; - timestamp = uint32(block.timestamp); - - return true; - } -} diff --git a/src/plugin/contracts/test/OracleLibraryTest.sol b/src/plugin/contracts/test/OracleLibraryTest.sol deleted file mode 100644 index 5a14520fe..000000000 --- a/src/plugin/contracts/test/OracleLibraryTest.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../libraries/integration/OracleLibrary.sol'; - -contract OracleLibraryTest { - function consult(address oracleAddress, uint32 period) public view returns (int24 timeWeightedAverageTick) { - timeWeightedAverageTick = OracleLibrary.consult(oracleAddress, period); - } - - function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) public pure returns (uint256 quoteAmount) { - quoteAmount = OracleLibrary.getQuoteAtTick(tick, baseAmount, baseToken, quoteToken); - } - - function lastTimepointMetadata(address oracleAddress) external view returns (uint16 index, uint32 timestamp) { - return OracleLibrary.lastTimepointMetadata(oracleAddress); - } - - function oldestTimepointMetadata(address oracleAddress) external view returns (uint16 index, uint32 timestamp) { - return OracleLibrary.oldestTimepointMetadata(oracleAddress); - } - - function isInitialized(address oracleAddress) external view returns (bool) { - return OracleLibrary.isInitialized(oracleAddress); - } - - // For gas snapshot test - function getGasCostOfConsult(address oracleAddress, uint32 period) public view returns (uint256) { - uint256 gasBefore = gasleft(); - OracleLibrary.consult(oracleAddress, period); - return gasBefore - gasleft(); - } - - function getGasCostOfGetQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) public view returns (uint256) { - uint256 gasBefore = gasleft(); - OracleLibrary.getQuoteAtTick(tick, baseAmount, baseToken, quoteToken); - return gasBefore - gasleft(); - } - - function isConnected(address oracleAddress, address poolAddress) external view returns (bool) { - return OracleLibrary.isOracleConnectedToPool(oracleAddress, poolAddress); - } -} diff --git a/src/plugin/contracts/test/SimulationAdaptiveFee.sol b/src/plugin/contracts/test/SimulationAdaptiveFee.sol deleted file mode 100644 index 621a4813f..000000000 --- a/src/plugin/contracts/test/SimulationAdaptiveFee.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol'; - -import '../libraries/AdaptiveFee.sol'; -import '../libraries/VolatilityOracle.sol'; -import '../types/AlgebraFeeConfigurationU144.sol'; -import './VolatilityOracleTest.sol'; - -/// @notice Contract used in simulations with historical data -contract SimulationAdaptiveFee is VolatilityOracleTest { - uint256 private constant UINT16_MODULO = 65536; - using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; - using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfiguration; - - AlgebraFeeConfigurationU144 private _feeConfig; - - constructor() VolatilityOracleTest() {} - - function init(int24 initTick, uint32 initTime) external { - initTime = initTime; - time = initTime; - tick = initTick; - - timepoints.initialize(initTime, tick); - - _feeConfig = AdaptiveFee.initialFeeConfiguration().pack(); - } - - function getFee() external view returns (uint16 fee, uint88 volatilityAverage, int24 averageTick) { - uint32 _time = time; - int24 _tick = tick; - uint16 lastIndex = index; - AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; - - uint16 oldestIndex = timepoints.getOldestIndex(lastIndex); - - volatilityAverage = timepoints.getAverageVolatility(_time, _tick, lastIndex, oldestIndex); - averageTick = timepoints[lastIndex].averageTick; - return (AdaptiveFee.getFee(volatilityAverage, feeConfig_), volatilityAverage, averageTick); - } - - function changeFeeConfiguration(AlgebraFeeConfiguration calldata _config) external { - AdaptiveFee.validateFeeConfiguration(_config); - _feeConfig = _config.pack(); // pack struct to uint144 and write in storage - } -} diff --git a/src/plugin/contracts/test/SlidingFeeTest.sol b/src/plugin/contracts/test/SlidingFeeTest.sol deleted file mode 100644 index d42303556..000000000 --- a/src/plugin/contracts/test/SlidingFeeTest.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../plugins/SlidingFeePlugin.sol'; - -contract SlidingFeeTest is SlidingFeePlugin { - event Fee(uint16 fee); - uint8 public constant override defaultPluginConfig = 0; - constructor(uint16 _baseFee) AlgebraBasePlugin(msg.sender, msg.sender, msg.sender) SlidingFeePlugin(_baseFee) {} - - function getFeeForSwap(bool zeroToOne, int24 lastTick, int24 currentTick) external returns (uint16 fee) { - fee = _getFeeAndUpdateFactors(zeroToOne, currentTick, lastTick); - emit Fee(fee); - } - - function getGasCostOfGetFeeForSwap(bool zeroToOne, int24 lastTick, int24 currentTick) external returns (uint256) { - unchecked { - uint256 gasBefore = gasleft(); - _getFeeAndUpdateFactors(zeroToOne, currentTick, lastTick); - return gasBefore - gasleft(); - } - } - - function changeBaseFee(uint16 newFee) external { - s_baseFee = newFee; - } - - function changeFactor(uint16 newFactor) external { - s_priceChangeFactor = newFactor; - } -} diff --git a/src/plugin/contracts/test/TestERC20.sol b/src/plugin/contracts/test/TestERC20.sol deleted file mode 100644 index d79858d4e..000000000 --- a/src/plugin/contracts/test/TestERC20.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; -pragma abicoder v1; - -import '@cryptoalgebra/integral-core/contracts/interfaces/IERC20Minimal.sol'; - -contract TestERC20 is IERC20Minimal { - mapping(address => uint256) public override balanceOf; - mapping(address => mapping(address => uint256)) public override allowance; - - constructor(uint256 amountToMint) { - mint(msg.sender, amountToMint); - } - - function mint(address to, uint256 amount) public { - unchecked { - uint256 balanceNext = balanceOf[to] + amount; - require(balanceNext >= amount, 'overflow balance'); - balanceOf[to] = balanceNext; - } - } - - function transfer(address recipient, uint256 amount) external override returns (bool) { - unchecked { - uint256 balanceBefore = balanceOf[msg.sender]; - require(balanceBefore >= amount, 'insufficient balance'); - balanceOf[msg.sender] = balanceBefore - amount; - - uint256 balanceRecipient = balanceOf[recipient]; - require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow'); - if (!isDeflationary) { - balanceOf[recipient] = balanceRecipient + amount; - } else { - balanceOf[recipient] = balanceRecipient + (amount - (amount * 5) / 100); - } - - emit Transfer(msg.sender, recipient, amount); - return true; - } - } - - function approve(address spender, uint256 amount) external override returns (bool) { - allowance[msg.sender][spender] = amount; - emit Approval(msg.sender, spender, amount); - return true; - } - - bool isDeflationary = false; - - function setDefl() external { - isDeflationary = true; - } - - function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { - unchecked { - uint256 allowanceBefore = allowance[sender][msg.sender]; - require(allowanceBefore >= amount, 'allowance insufficient'); - - allowance[sender][msg.sender] = allowanceBefore - amount; - - uint256 balanceRecipient = balanceOf[recipient]; - require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient'); - if (!isDeflationary) { - balanceOf[recipient] = balanceRecipient + amount; - } else { - balanceOf[recipient] = balanceRecipient + (amount - (amount * 5) / 100); - } - uint256 balanceSender = balanceOf[sender]; - require(balanceSender >= amount, 'underflow balance sender'); - balanceOf[sender] = balanceSender - amount; - - emit Transfer(sender, recipient, amount); - return true; - } - } -} diff --git a/src/plugin/contracts/test/TestVirtualPool.sol b/src/plugin/contracts/test/TestVirtualPool.sol deleted file mode 100644 index ee6d4bdd7..000000000 --- a/src/plugin/contracts/test/TestVirtualPool.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; -pragma abicoder v1; - -import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; -import '../interfaces/IAlgebraVirtualPool.sol'; - -contract TestVirtualPool is IAlgebraVirtualPool { - struct Data { - int24 tick; - } - - Data[] private data; - - function crossTo(int24, bool) external override returns (bool) { - for (uint i; i < 100; i++) { - (, int24 poolTick, , , , ) = IAlgebraPool(msg.sender).globalState(); - data.push(Data(poolTick)); - } - - return true; - } -} diff --git a/src/plugin/contracts/test/VolatilityOracleTest.sol b/src/plugin/contracts/test/VolatilityOracleTest.sol deleted file mode 100644 index 48d39e509..000000000 --- a/src/plugin/contracts/test/VolatilityOracleTest.sol +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../libraries/VolatilityOracle.sol'; - -contract VolatilityOracleTest { - uint256 private constant UINT16_MODULO = 65536; - using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; - - VolatilityOracle.Timepoint[UINT16_MODULO] public timepoints; - - uint32 initTime; - uint32 public time; - int24 public tick; - uint16 public index; - uint32 private step = 13; - - struct InitializeParams { - uint32 time; - int24 tick; - } - - function initialize(InitializeParams calldata params) external { - initTime = params.time; - time = params.time; - tick = params.tick; - timepoints.initialize(params.time, tick); - } - - function setState(uint32 _time, uint16 _index) external { - time = _time; - index = _index; - } - - function writeTimepointDirectly(uint16 _index, VolatilityOracle.Timepoint memory timepoint) external { - timepoints[_index] = timepoint; - } - - function setStep(uint32 newStep) external { - step = newStep; - } - - function advanceTime(uint32 by) public { - unchecked { - time += by; - } - } - - struct UpdateParams { - uint32 advanceTimeBy; - int24 tick; - } - - // write a timepoint, then change tick and liquidity - function update(UpdateParams calldata params) external { - uint16 _index = index; - uint32 _time = time; - int24 _tick = tick; - unchecked { - _time += params.advanceTimeBy; - } - - (_index, ) = timepoints.write(_index, _time, _tick); - _tick = params.tick; - - tick = _tick; - index = _index; - time = _time; - } - - function batchUpdate(UpdateParams[] calldata params) external { - // sload everything - int24 _tick = tick; - uint16 _index = index; - uint32 _time = time; - unchecked { - for (uint256 i; i < params.length; ++i) { - _time += params[i].advanceTimeBy; - (_index, ) = timepoints.write(_index, _time, _tick); - _tick = params[i].tick; - } - } - - // sstore everything - tick = _tick; - index = _index; - time = _time; - } - - struct UpdateParamsFixedTimedelta { - int24 tick; - } - - function batchUpdateFast(uint256 length) external { - // sload everything - int24 _tick = tick; - uint32 _time = time; - uint32 STEP = step; - - uint32 _initTime = initTime; - uint256 _index = (_time - _initTime) / STEP; - - VolatilityOracle.Timepoint memory last = timepoints[uint16(_index)]; - - unchecked { - for (uint256 i; i < length; ++i) { - _time += STEP; - - // get next index considering overflow - uint16 nextIndex = uint16(_index + 1); - - int24 avgTick; - uint16 windowStartIndex; - - if (_time - _initTime > 24 hours) { - windowStartIndex = uint16(_index - (uint256(24 hours) / STEP) + 1); // CHECK - avgTick = -int24(uint24((STEP * ((nextIndex + 1) * nextIndex - (windowStartIndex + 1) * windowStartIndex)) / (2 * uint256(24 hours)))); - } else { - uint32 timeDelta = _time - _initTime; - avgTick = -int24(uint24((STEP * (nextIndex + 1) * nextIndex) / (2 * uint256(timeDelta)))); - } - - last = VolatilityOracle._createNewTimepoint(last, _time, _tick, avgTick, windowStartIndex); - - if ((_index + 1) - uint256(_index - (uint256(24 hours) / STEP) + 1) > type(uint16).max) windowStartIndex = uint16(_index + 2); - timepoints[uint16(nextIndex)] = last; - - _tick--; - - _index = _index + 1; - } - } - - // sstore everything - tick = _tick; - index = uint16(_index); - time = _time; - } - - function batchUpdateFixedTimedelta(uint256 length) external { - // sload everything - int24 _tick = tick; - uint16 _index = index; - uint32 _time = time; - uint32 STEP = step; - unchecked { - for (uint256 i; i < length; ++i) { - _time += STEP; - (_index, ) = timepoints.write(_index, _time, _tick); - _tick--; - } - } - - // sstore everything - tick = _tick; - index = _index; - time = _time; - } - - function getTimepoints( - uint32[] calldata secondsAgos - ) external view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { - return timepoints.getTimepoints(time, secondsAgos, tick, index); - } - - function getOldestIndex() external view returns (uint16 oldestIndex) { - return timepoints.getOldestIndex(index); - } - - function getGasCostOfGetPoints(uint32[] calldata secondsAgos) external view returns (uint256) { - (uint32 _time, int24 _tick, uint16 _index) = (time, tick, index); - unchecked { - uint256 gasBefore = gasleft(); - timepoints.getTimepoints(_time, secondsAgos, _tick, _index); - return gasBefore - gasleft(); - } - } - - function volatilityOnRange(uint32 dt, int24 tick0, int24 tick1, int24 avgTick0, int24 avgTick1) external pure returns (uint256) { - return VolatilityOracle._volatilityOnRange(int256(uint256(dt)), tick0, tick1, avgTick0, avgTick1); - } - - function getAverageVolatility() external view returns (uint88) { - uint16 lastIndex = index; - uint16 oldestIndex = timepoints.getOldestIndex(lastIndex); - return timepoints.getAverageVolatility(time, tick, lastIndex, oldestIndex); - } - - function getAverageTick() external view returns (int256) { - uint32 lastTimestamp = timepoints[index].blockTimestamp; - int56 lastTickCumulative = timepoints[index].tickCumulative; - - uint16 oldestIndex; - if (timepoints[index + 1].initialized) { - oldestIndex = index + 1; - } - - (uint32 _time, int24 _tick, uint16 _index) = (time, tick, index); - (int256 avgTick, ) = timepoints._getAverageTick(_time, _tick, _index, oldestIndex, lastTimestamp, lastTickCumulative); - return int24(avgTick); - } - - function getTickCumulativeAt(uint32 secondsAgo) external view returns (int256) { - uint16 oldestIndex; - if (timepoints[index + 1].initialized) { - oldestIndex = index + 1; - } - - (uint32 _time, int24 _tick, uint16 _index) = (time, tick, index); - (int56 tickCumulative, ) = timepoints._getTickCumulativeAt(_time, secondsAgo, _tick, _index, oldestIndex); - return int56(tickCumulative); - } - - function window() external pure returns (uint256) { - return VolatilityOracle.WINDOW; - } -} diff --git a/src/plugin/contracts/test/echidna/AdaptiveFeeEchidnaTest.sol b/src/plugin/contracts/test/echidna/AdaptiveFeeEchidnaTest.sol deleted file mode 100644 index 5da1e7949..000000000 --- a/src/plugin/contracts/test/echidna/AdaptiveFeeEchidnaTest.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import '../../base/AlgebraFeeConfiguration.sol'; -import '../../libraries/AdaptiveFee.sol'; - -contract AdaptiveFeeEchidnaTest { - using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfiguration; - - function checkExpInvariants(uint256 x, uint16 gamma) external pure { - unchecked { - require(gamma != 0); - if (x >= 6 * gamma) return; - uint256 g4 = uint256(gamma) ** 4; - uint256 exp = AdaptiveFee.expXg4(x, gamma, g4); - assert(exp < 2 ** 137); - } - } - - function checkSigmoidInvariants(uint256 x, uint16 gamma, uint16 alpha, uint256 beta) external pure { - require(gamma != 0); - uint256 res = AdaptiveFee.sigmoid(x, gamma, alpha, beta); - assert(res <= type(uint16).max); - assert(res <= alpha); - } - - function checkFeeInvariants( - uint88 volatility, - uint16 alpha1, - uint16 alpha2, - uint32 beta1, - uint32 beta2, - uint16 gamma1, - uint16 gamma2, - uint16 baseFee - ) external pure returns (uint256 fee) { - unchecked { - AlgebraFeeConfiguration memory feeConfig = AlgebraFeeConfiguration(alpha1, alpha2, beta1, beta2, gamma1, gamma2, baseFee); - AdaptiveFee.validateFeeConfiguration(feeConfig); - - fee = AdaptiveFee.getFee(volatility, feeConfig.pack()); - assert(fee <= type(uint16).max); - assert(fee >= baseFee); - assert(fee <= baseFee + alpha1 + alpha2); - } - } - - function checkFeeConfigPackedCorrectness( - uint16 alpha1, - uint16 alpha2, - uint32 beta1, - uint32 beta2, - uint16 gamma1, - uint16 gamma2, - uint16 baseFee - ) external pure { - AlgebraFeeConfigurationU144 feeConfig = AlgebraFeeConfiguration(alpha1, alpha2, beta1, beta2, gamma1, gamma2, baseFee).pack(); - - assert(feeConfig.alpha1() == alpha1); - assert(feeConfig.alpha2() == alpha2); - assert(feeConfig.beta1() == beta1); - assert(feeConfig.beta2() == beta2); - assert(feeConfig.gamma1() == gamma1); - assert(feeConfig.gamma2() == gamma2); - assert(feeConfig.baseFee() == baseFee); - } -} diff --git a/src/plugin/contracts/test/echidna/VolatilityOracleEchidnaTest.sol b/src/plugin/contracts/test/echidna/VolatilityOracleEchidnaTest.sol deleted file mode 100644 index 7d60f3f1c..000000000 --- a/src/plugin/contracts/test/echidna/VolatilityOracleEchidnaTest.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import './../VolatilityOracleTest.sol'; - -contract VolatilityOracleEchidnaTest { - uint256 internal constant UINT16_MODULO = 65536; - - VolatilityOracleTest private volatilityOracle; - - bool private initialized; - uint32 private timePassed; - - constructor() { - volatilityOracle = new VolatilityOracleTest(); - } - - function initialize(uint32 time, int24 tick) external { - require(!initialized); - initialized = true; - if (tick % 60 != 0) tick = (tick / 60) * 60; - volatilityOracle.initialize(VolatilityOracleTest.InitializeParams({time: time, tick: tick})); - } - - function _limitTimePassed(uint32 by) private { - unchecked { - require(timePassed + by >= timePassed); - timePassed += by; - } - } - - function advanceTime(uint32 by) public { - _limitTimePassed(by); - volatilityOracle.advanceTime(by); - } - - // write a timepoint, then change tick and liquidity - function update(uint32 advanceTimeBy, int24 tick) external { - require(initialized); - _limitTimePassed(advanceTimeBy); - volatilityOracle.update(VolatilityOracleTest.UpdateParams({advanceTimeBy: advanceTimeBy, tick: tick})); - } - - function checkAveragesNotOverflow() external view { - require(initialized); - int256 tick = volatilityOracle.getAverageTick(); - assert(tick <= type(int24).max); - assert(tick >= type(int24).min); - } - - function checkTimeWeightedResultAssertions(uint32 secondsAgo0, uint32 secondsAgo1) external view { - unchecked { - require(initialized); - // secondsAgo0 should be the larger one - if (secondsAgo0 < secondsAgo1) (secondsAgo0, secondsAgo1) = (secondsAgo1, secondsAgo0); - - uint32 timeElapsed = secondsAgo0 - secondsAgo1; - if (timeElapsed > 0) { - uint32[] memory secondsAgos = new uint32[](2); - secondsAgos[0] = secondsAgo0; - secondsAgos[1] = secondsAgo1; - - (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) = volatilityOracle.getTimepoints(secondsAgos); - - int56 timeWeightedTick = (tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(timeElapsed)); - uint112 averageVolatility = (uint112(volatilityCumulatives[1]) - uint112(volatilityCumulatives[0])) / uint112(timeElapsed); - - assert(timeWeightedTick <= type(int24).max); - assert(timeWeightedTick >= type(int24).min); - - uint32 currentTime = volatilityOracle.time(); - (, uint32 lastTimestamp, , , , , ) = volatilityOracle.timepoints(volatilityOracle.index()); - - // we should not compare volatilityCumulative for timestamps after last timepoint - if (!(currentTime - secondsAgo0 >= lastTimestamp && currentTime - secondsAgo1 >= lastTimestamp)) { - assert(averageVolatility <= type(uint88).max); - } - } - } - } - - function checkTwoAdjacentTimepointsTickCumulativeModTimeElapsedAlways0(uint16 index) external view { - unchecked { - require(initialized); - // check that the timepoints are initialized, and that the index is not the oldest timepoint - uint16 oldestIndex = volatilityOracle.getOldestIndex(); - require(index != oldestIndex); - - (bool initialized0, uint32 blockTimestamp0, int56 tickCumulative0, , , , ) = volatilityOracle.timepoints(index - 1); - (bool initialized1, uint32 blockTimestamp1, int56 tickCumulative1, , , , ) = volatilityOracle.timepoints(index); - - if (!initialized0 || !initialized1) return; - - uint32 timeElapsed = blockTimestamp1 - blockTimestamp0; - assert(timeElapsed > 0); - assert((tickCumulative1 - tickCumulative0) % int56(uint56(timeElapsed)) == 0); - } - } - - function checkTimeWeightedAveragesAlwaysFitsType(uint32 secondsAgo) external view { - require(initialized); - if (secondsAgo == 0) return; - uint32[] memory secondsAgos = new uint32[](2); - secondsAgos[0] = secondsAgo; - secondsAgos[1] = 0; - (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) = volatilityOracle.getTimepoints(secondsAgos); - - // compute the time weighted tick, rounded towards negative infinity - unchecked { - int56 numerator = tickCumulatives[1] - tickCumulatives[0]; - int56 timeWeightedTick = numerator / int56(uint56(secondsAgo)); - if (numerator < 0 && numerator % int56(uint56(secondsAgo)) != 0) { - timeWeightedTick--; - } - - uint112 volatility = (uint112(volatilityCumulatives[1]) - uint112(volatilityCumulatives[0])) / uint112(secondsAgo); - - // the time weighted averages fit in their respective accumulated types - assert(timeWeightedTick <= type(int24).max && timeWeightedTick >= type(int24).min); - - uint32 currentTime = volatilityOracle.time(); - (, uint32 lastTimestamp, , , , , ) = volatilityOracle.timepoints(volatilityOracle.index()); - - // we should not compare volatilityCumulative for timestamps after last timepoint - if (!(currentTime >= lastTimestamp && currentTime - secondsAgo >= lastTimestamp)) { - assert(volatility <= type(uint88).max); - } - } - } -} diff --git a/src/plugin/contracts/test/echidna/VolatilityOracleMathEchidnaTest.sol b/src/plugin/contracts/test/echidna/VolatilityOracleMathEchidnaTest.sol deleted file mode 100644 index 5f37c31f7..000000000 --- a/src/plugin/contracts/test/echidna/VolatilityOracleMathEchidnaTest.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.20; - -import './../../libraries/VolatilityOracle.sol'; - -contract VolatilityOracleMathEchidnaTest { - /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 - int24 internal constant MIN_TICK = -887272; - /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 - int24 internal constant MAX_TICK = -MIN_TICK; - - function checkVolatilityOnRange(uint32 dt, int24 tick0, int24 tick1, int24 avgTick0, int24 avgTick1) external pure { - uint256 volatility = VolatilityOracle._volatilityOnRange(int256(uint256(dt)), tick0, tick1, avgTick0, avgTick1); - assert(volatility <= type(uint88).max); - } - - function checkLteConsideringOverflow(uint64 a, uint64 b, uint64 currentTime) external pure { - require(a <= currentTime); - require(b <= currentTime); - require(currentTime - a <= type(uint32).max); - require(currentTime - b <= type(uint32).max); - - bool res = VolatilityOracle._lteConsideringOverflow(uint32(a), uint32(b), uint32(currentTime)); - - if ((a <= currentTime && b <= currentTime) || (a > currentTime && b > currentTime)) { - assert(res == a <= b); - } - } -} diff --git a/src/plugin/contracts/test/echidna/echidna.config.yml b/src/plugin/contracts/test/echidna/echidna.config.yml deleted file mode 100644 index d5fa19489..000000000 --- a/src/plugin/contracts/test/echidna/echidna.config.yml +++ /dev/null @@ -1,75 +0,0 @@ -#format can be "text" or "json" for different output (human or machine readable) -format: 'text' -#checkAsserts checks assertions -testMode: 'assertion' -#coverage controls coverage guided testing -coverage: false -# #psender is the sender for property transactions; by default intentionally -# #the same as contract deployer -# psender: "0x00a329c0648769a73afac7f9381e08fb43dbea70" -# #prefix is the prefix for Boolean functions that are properties to be checked -# prefix: "echidna_" -# #propMaxGas defines gas cost at which a property fails -# propMaxGas: 8000030 -# #testMaxGas is a gas limit; does not cause failure, but terminates sequence -# testMaxGas: 8000030 -# #maxGasprice is the maximum gas price -# maxGasprice: 100000000000 -# #testLimit is the number of test sequences to run -# testLimit: 50000 -# #stopOnFail makes echidna terminate as soon as any property fails and has been shrunk -# stopOnFail: false -# #estimateGas makes echidna perform analysis of maximum gas costs for functions (experimental) -# estimateGas: false -# #seqLen defines how many transactions are in a test sequence -# seqLen: 100 -# #shrinkLimit determines how much effort is spent shrinking failing sequences -# shrinkLimit: 5000 -# #contractAddr is the address of the contract itself -# contractAddr: "0x00a329c0648769a73afac7f9381e08fb43dbea72" -# #deployer is address of the contract deployer (who often is privileged owner, etc.) -# deployer: "0x00a329c0648769a73afac7f9381e08fb43dbea70" -# #sender is set of addresses transactions may originate from -# sender: ["0x10000", "0x20000", "0x00a329c0648769a73afac7f9381e08fb43dbea70"] -# #balanceAddr is default balance for addresses -# balanceAddr: 0xffffffff -# #balanceContract overrides balanceAddr for the contract address -# balanceContract: 0 -# #solcArgs allows special args to solc -# solcArgs: "--allow-paths node_modules/@cryptoalgebra/integral-core/contracts/libraries/Constants.sol" -# #solcLibs is solc libraries -# solcLibs: [] -cryticArgs: ['--hardhat-ignore-compile'] -# #cryticArgs allows special args to crytic -# cryticArgs: [] -# #quiet produces (much) less verbose output -# quiet: false -# #initialize the blockchain with some data -# initialize: null -# #whether or not to use the multi-abi mode of testing -# multi-abi: false -# #benchmarkMode enables benchmark mode -# benchmarkMode: false -# #timeout controls test timeout settings -# timeout: null -# #seed not defined by default, is the random seed -# #seed: 0 -# #dictFreq controls how often to use echidna's internal dictionary vs random -# #values -# dictFreq: 0.40 -# maxTimeDelay: 604800 -# #maximum time between generated txs; default is one week -# maxBlockDelay: 60480 -# #maximum number of blocks elapsed between generated txs; default is expected increment in one week -# # timeout: -# #campaign timeout (in seconds) -# # list of methods to filter -# filterFunctions: [] -# # by default, blacklist methods in filterFunctions -# filterBlacklist: true -# #directory to save the corpus; by default is disabled -# corpusDir: null -# # constants for corpus mutations (for experimentation only) -# mutConsts: [100, 1, 1] -# # maximum value to send to payable functions -# maxValue: 100000000000000000000 # 100 eth diff --git a/src/plugin/contracts/types/AlgebraFeeConfigurationU144.sol b/src/plugin/contracts/types/AlgebraFeeConfigurationU144.sol deleted file mode 100644 index 64881a8ef..000000000 --- a/src/plugin/contracts/types/AlgebraFeeConfigurationU144.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.20; - -import '../base/AlgebraFeeConfiguration.sol'; - -type AlgebraFeeConfigurationU144 is uint144; -using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfigurationU144 global; - -/// @title AdaptiveFee packed configuration library -/// @notice Used to interact with uint144-packed fee config -/// @dev Structs are not packed in storage with neighboring values, but uint144 can be packed -library AlgebraFeeConfigurationU144Lib { - uint256 private constant UINT16_MASK = 0xFFFF; - uint256 private constant UINT32_MASK = 0xFFFFFFFF; - - // alpha1 offset is 0 - uint256 private constant ALPHA2_OFFSET = 16; - uint256 private constant BETA1_OFFSET = 32; - uint256 private constant BETA2_OFFSET = 64; - uint256 private constant GAMMA1_OFFSET = 96; - uint256 private constant GAMMA2_OFFSET = 112; - uint256 private constant BASE_FEE_OFFSET = 128; - - function pack(AlgebraFeeConfiguration memory config) internal pure returns (AlgebraFeeConfigurationU144) { - uint144 _config = uint144( - (uint256(config.baseFee) << BASE_FEE_OFFSET) | - (uint256(config.gamma2) << GAMMA2_OFFSET) | - (uint256(config.gamma1) << GAMMA1_OFFSET) | - (uint256(config.beta2) << BETA2_OFFSET) | - (uint256(config.beta1) << BETA1_OFFSET) | - (uint256(config.alpha2) << ALPHA2_OFFSET) | - uint256(config.alpha1) - ); - - return AlgebraFeeConfigurationU144.wrap(_config); - } - - function alpha1(AlgebraFeeConfigurationU144 config) internal pure returns (uint16 _alpha1) { - assembly { - _alpha1 := and(UINT16_MASK, config) - } - } - - function alpha2(AlgebraFeeConfigurationU144 config) internal pure returns (uint16 _alpha2) { - assembly { - _alpha2 := and(UINT16_MASK, shr(ALPHA2_OFFSET, config)) - } - } - - function beta1(AlgebraFeeConfigurationU144 config) internal pure returns (uint32 _beta1) { - assembly { - _beta1 := and(UINT32_MASK, shr(BETA1_OFFSET, config)) - } - } - - function beta2(AlgebraFeeConfigurationU144 config) internal pure returns (uint32 _beta2) { - assembly { - _beta2 := and(UINT32_MASK, shr(BETA2_OFFSET, config)) - } - } - - function gamma1(AlgebraFeeConfigurationU144 config) internal pure returns (uint16 _gamma1) { - assembly { - _gamma1 := and(UINT16_MASK, shr(GAMMA1_OFFSET, config)) - } - } - - function gamma2(AlgebraFeeConfigurationU144 config) internal pure returns (uint16 _gamma2) { - assembly { - _gamma2 := and(UINT16_MASK, shr(GAMMA2_OFFSET, config)) - } - } - - function baseFee(AlgebraFeeConfigurationU144 config) internal pure returns (uint16 _baseFee) { - assembly { - _baseFee := and(UINT16_MASK, shr(BASE_FEE_OFFSET, config)) - } - } -} diff --git a/src/plugin/echidna.config.yml b/src/plugin/echidna.config.yml deleted file mode 100644 index 35b3e652a..000000000 --- a/src/plugin/echidna.config.yml +++ /dev/null @@ -1,74 +0,0 @@ -#format can be "text" or "json" for different output (human or machine readable) -format: 'text' -#checkAsserts checks assertions -testMode: "assertion" -#coverage controls coverage guided testing -coverage: false -# #psender is the sender for property transactions; by default intentionally -# #the same as contract deployer -# psender: "0x00a329c0648769a73afac7f9381e08fb43dbea70" -# #prefix is the prefix for Boolean functions that are properties to be checked -# prefix: "echidna_" -# #propMaxGas defines gas cost at which a property fails -# propMaxGas: 8000030 -# #testMaxGas is a gas limit; does not cause failure, but terminates sequence -# testMaxGas: 8000030 -# #maxGasprice is the maximum gas price -# maxGasprice: 100000000000 -# #testLimit is the number of test sequences to run -# testLimit: 50000 -# #stopOnFail makes echidna terminate as soon as any property fails and has been shrunk -# stopOnFail: false -# #estimateGas makes echidna perform analysis of maximum gas costs for functions (experimental) -# estimateGas: false -# #seqLen defines how many transactions are in a test sequence -# seqLen: 100 -# #shrinkLimit determines how much effort is spent shrinking failing sequences -# shrinkLimit: 5000 -# #contractAddr is the address of the contract itself -# contractAddr: "0x00a329c0648769a73afac7f9381e08fb43dbea72" -# #deployer is address of the contract deployer (who often is privileged owner, etc.) -# deployer: "0x00a329c0648769a73afac7f9381e08fb43dbea70" -# #sender is set of addresses transactions may originate from -# sender: ["0x10000", "0x20000", "0x00a329c0648769a73afac7f9381e08fb43dbea70"] -# #balanceAddr is default balance for addresses -# balanceAddr: 0xffffffff -# #balanceContract overrides balanceAddr for the contract address -# balanceContract: 0 -# #solcArgs allows special args to solc -# solcArgs: "" -# #solcLibs is solc libraries -# solcLibs: [] -# #cryticArgs allows special args to crytic -# cryticArgs: [] -# #quiet produces (much) less verbose output -# quiet: false -# #initialize the blockchain with some data -# initialize: null -# #whether or not to use the multi-abi mode of testing -# multi-abi: false -# #benchmarkMode enables benchmark mode -# benchmarkMode: false -# #timeout controls test timeout settings -# timeout: null -# #seed not defined by default, is the random seed -# #seed: 0 -# #dictFreq controls how often to use echidna's internal dictionary vs random -# #values -# dictFreq: 0.40 -# maxTimeDelay: 604800 -# #maximum time between generated txs; default is one week -# maxBlockDelay: 60480 -# #maximum number of blocks elapsed between generated txs; default is expected increment in one week -# # timeout: -# #campaign timeout (in seconds) -# # list of methods to filter -# filterFunctions: [] -# # by default, blacklist methods in filterFunctions -# filterBlacklist: true -# #directory to save the corpus; by default is disabled -# corpusDir: null -# # constants for corpus mutations (for experimentation only) -# mutConsts: [100, 1, 1] -# # maximum value to send to payable functions -# maxValue: 100000000000000000000 # 100 eth diff --git a/src/plugin/hardhat.config.ts b/src/plugin/hardhat.config.ts deleted file mode 100644 index de15963bf..000000000 --- a/src/plugin/hardhat.config.ts +++ /dev/null @@ -1,89 +0,0 @@ -import '@nomicfoundation/hardhat-toolbox'; -import 'hardhat-output-validator'; -import 'hardhat-contract-sizer'; -import 'solidity-docgen'; -import { SolcUserConfig } from 'hardhat/types'; -import baseConfig from '../../hardhat.base.config'; - -const HIGHEST_OPTIMIZER_COMPILER_SETTINGS: SolcUserConfig = { - version: '0.8.20', - settings: { - evmVersion: 'paris', - optimizer: { - enabled: true, - runs: 1_000_000, - }, - metadata: { - bytecodeHash: 'none', - }, - }, -}; - -const DEFAULT_COMPILER_SETTINGS: SolcUserConfig = { - version: '0.8.20', - settings: { - evmVersion: 'paris', - optimizer: { - enabled: true, - runs: 200, - }, - metadata: { - bytecodeHash: 'none', - }, - }, -}; - -const LOWEST_COMPILER_SETTINGS: SolcUserConfig = { - version: '0.8.20', - settings: { - evmVersion: 'paris', - optimizer: { - enabled: true, - runs: 0, - }, - metadata: { - bytecodeHash: 'none', - }, - }, -}; - -if (process.env.RUN_COVERAGE == '1') { - /** - * Updates the default compiler settings when running coverage. - * - * See https://github.com/sc-forks/solidity-coverage/issues/417#issuecomment-730526466 - */ - console.info('Using coverage compiler settings'); - const details = { - yul: true, - yulDetails: { - stackAllocation: true, - }, - }; - - HIGHEST_OPTIMIZER_COMPILER_SETTINGS.settings.details = details; - DEFAULT_COMPILER_SETTINGS.settings.details = details; -} - -export default { - networks: baseConfig.networks, - etherscan: baseConfig.etherscan, - typechain: { - outDir: 'typechain', - }, - solidity: { - compilers: [HIGHEST_OPTIMIZER_COMPILER_SETTINGS], - }, - docgen: { - outputDir: '../../docs/Contracts/Plugin', - pages: (x: any, buildInfo: any) => { - return `${buildInfo.relativePath}`.replace('.sol', '.md'); - }, - templates: '../../docs/doc_templates/public', - collapseNewlines: true, - }, - outputValidator: { - runOnCompile: false, - exclude: ['contracts/test'], - }, -}; diff --git a/src/plugin/package-lock.json b/src/plugin/package-lock.json deleted file mode 100644 index dab0265b5..000000000 --- a/src/plugin/package-lock.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "@cryptoalgebra/integral-base-plugin", - "version": "1.2.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@cryptoalgebra/integral-base-plugin", - "version": "1.2.1", - "license": "GPL-2.0-or-later", - "dependencies": { - "@cryptoalgebra/integral-core": "1.2.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "../periphery": { - "name": "@cryptoalgebra/integral-periphery", - "version": "1.2.1", - "extraneous": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@cryptoalgebra/integral-core": "1.2.1", - "@openzeppelin/contracts": "4.9.3", - "@uniswap/v2-core": "1.0.1" - }, - "devDependencies": { - "is-svg": "^4.3.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@cryptoalgebra/integral-core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.2.1.tgz", - "integrity": "sha512-+Lj7y2ZqpJ58fNtqTRvrMv/HPcyqNkMpINu9LQK6/iMDePmzNoywogLLiGU34MlxAaGEwO6XNWM7ENq/YzzZzA==", - "dependencies": { - "@openzeppelin/contracts": "4.9.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@openzeppelin/contracts": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.3.tgz", - "integrity": "sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==" - } - } -} diff --git a/src/plugin/package.json b/src/plugin/package.json deleted file mode 100644 index 202ff54ff..000000000 --- a/src/plugin/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@cryptoalgebra/integral-base-plugin", - "description": "Default plugin for Algebra Integral core", - "license": "GPL-2.0-or-later", - "publishConfig": { - "access": "public" - }, - "version": "1.2.4", - "keywords": [ - "algebra" - ], - "files": [ - "artifacts/contracts/AlgebraBasePluginV1.sol/**/*.json", - "artifacts/contracts/AlgebraBasePluginV2.sol/**/*.json", - "artifacts/contracts/BasePluginV1Factory.sol/**/*.json", - "artifacts/contracts/BasePluginV2Factory.sol/**/*.json", - "!artifacts/contracts/**/*.dbg.json", - "contracts/libraries/integration/OracleLibrary.sol", - "contracts/interfaces/plugins/*", - "contracts/base/BasePlugin.sol", - "contracts/base/BasePluginFactory.sol", - "contracts/interfaces/**/*.sol", - "contracts/base/AlgebraFeeConfiguration.sol" - ], - "repository": { - "type": "git", - "url": "https://github.com/cryptoalgebra/Algebra/" - }, - "dependencies": { - "@cryptoalgebra/integral-core": "~1.2.8", - "@cryptoalgebra/integral-periphery": "~1.2.4" - }, - "scripts": { - "precommit": "pretty-quick --staged --pattern '**/*.sol' && hardhat compile", - "compile": "npm --prefix ../core run compile && npm --prefix ../periphery run compile && hardhat compile", - "solhint": "solhint --config ../../.solhint.json \"contracts/**/*.sol\"", - "test": "hardhat test --parallel", - "coverage": "hardhat coverage --solcoverjs ./.solcover.js" - }, - "engines": { - "npm": ">=10.0.0", - "node": ">=18.18.0" - } -} diff --git a/src/plugin/scripts/deploy.js b/src/plugin/scripts/deploy.js deleted file mode 100644 index fa1ac5a91..000000000 --- a/src/plugin/scripts/deploy.js +++ /dev/null @@ -1,34 +0,0 @@ -const hre = require("hardhat"); -const fs = require('fs'); -const path = require('path'); - -async function main() { - - const deployDataPath = path.resolve(__dirname, '../../../deploys.json') - const deploysData = JSON.parse(fs.readFileSync(deployDataPath, 'utf8')) - - const BasePluginV1Factory = await hre.ethers.getContractFactory("BasePluginV1Factory"); - const dsFactory = await BasePluginV1Factory.deploy(deploysData.factory); - - await dsFactory.waitForDeployment() - - console.log("PluginFactory to:", dsFactory.target); - - const factory = await hre.ethers.getContractAt('IAlgebraFactory', deploysData.factory) - - await factory.setDefaultPluginFactory(dsFactory.target) - console.log('Updated plugin factory address in factory') - - deploysData.BasePluginV1Factory = dsFactory.target; - fs.writeFileSync(deployDataPath, JSON.stringify(deploysData), 'utf-8'); - -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); \ No newline at end of file diff --git a/src/plugin/scripts/simulation/.gitignore b/src/plugin/scripts/simulation/.gitignore deleted file mode 100644 index df9a749e6..000000000 --- a/src/plugin/scripts/simulation/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -swaps.json -simulationResult.json \ No newline at end of file diff --git a/src/plugin/scripts/simulation/AdaptiveFeeSimulation.ts b/src/plugin/scripts/simulation/AdaptiveFeeSimulation.ts deleted file mode 100644 index 71688b0f5..000000000 --- a/src/plugin/scripts/simulation/AdaptiveFeeSimulation.ts +++ /dev/null @@ -1,98 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { ethers } from 'hardhat'; -import { SimulationAdaptiveFee } from '../../typechain'; -import { Wallet } from 'ethers'; - -const deployContract = async (initTick: string, initTime: string) => { - const volatilityOracleTestFactory = await ethers.getContractFactory('SimulationAdaptiveFee'); - const contract = (await volatilityOracleTestFactory.deploy()) as any as SimulationAdaptiveFee; - await contract.init(initTick, initTime); - return contract; -}; - -type SimulationRecord = { - timestamp: number; - tick: number; - fee: number; - volatilityAverage: string; - tickAverage: number; - gasUsed: string; -}; - -async function doSimulation(timeout: number) { - const [wallet] = (await ethers.getSigners()) as [any] as [Wallet]; - //await ethers.provider.send('hardhat_setBalance', [wallet.address, '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000']); - const events = JSON.parse(fs.readFileSync(path.resolve(__dirname, './external-data/swaps.json')).toString()); - - let lastTimestamp = events[0].timestamp; - let lastTick = events[0].tick; - - const contract = await deployContract(lastTick, lastTimestamp); - await contract.waitForDeployment(); - console.log('Deployed'); - - let i = 0; - let packNum = 0; - const records: SimulationRecord[] = []; - const startTime = Math.floor(Date.now() / 1000); - - let gasCumulative = 0n; - - console.log(`Starting simulation. Amount of swaps: ${events.length}`); - - for (const swap of events) { - try { - if (lastTimestamp != Number(swap.timestamp)) { - //await ethers.provider.send('evm_mine'); - const tx = await contract.update({ - advanceTimeBy: Number(swap.timestamp) - lastTimestamp, - tick: lastTick, - }); - - lastTimestamp = Number(swap.timestamp); - - let gasUsed = Number((await tx.wait())?.gasUsed) - 21000; - gasCumulative += BigInt(gasUsed); - const res = await contract.getFee(); - - records.push({ - timestamp: Number(swap.timestamp), - tick: Number(swap.tick), - fee: Number(res.fee), - volatilityAverage: res.volatilityAverage.toString(), - tickAverage: Number(res.averageTick), - gasUsed: gasUsed.toString(), - }); - } - lastTick = swap.tick; - i++; - if (Math.floor(i / 1000) != packNum) { - const timeNow = Math.floor(Date.now() / 1000); - packNum = Math.floor(i / 1000); - const speed = i / (timeNow - startTime); - const timeEstimation = Math.floor((events.length - i) / (speed > 0 ? speed : 1)); - const estimatedHours = Math.floor(timeEstimation / (60 * 60)); - const estimatedMinutes = Math.floor((timeEstimation % (60 * 60)) / 60); - const estimatedSeconds = Math.floor((timeEstimation % (60 * 60)) % 60); - const avgGas = gasCumulative / BigInt(i); - console.log(`Done ${i} / ${events.length}, avg gas: ${avgGas}, est time: ${estimatedHours}:${estimatedMinutes}:${estimatedSeconds}`); - - if (timeNow - startTime > timeout) { - console.log(`Finishing by timeout: ${i} \ ${events.length}`); - break; - } - } - } catch (e) { - console.log('ERROR', e); - break; - } - } - - console.log('Done!'); - fs.writeFileSync(path.resolve(__dirname, 'simulationResult.json'), JSON.stringify(records)); -} - -doSimulation(5 * 60 * 60) - .then() - .catch((x) => console.log('FAILED', x)); diff --git a/src/plugin/scripts/simulation/README.md b/src/plugin/scripts/simulation/README.md deleted file mode 100644 index 59eff9982..000000000 --- a/src/plugin/scripts/simulation/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## Simulation scripts - -This directory contains scripts that can be used for simulations using historical data. It is better to run with `localGeth` network, using local geth node in `dev` mode (Built-in hardhat node can be slower and crash due to memory usage). - -For large-scale simulations, it is better to use a much more efficient tool: https://github.com/cryptoalgebra/IntegralFeeSimulation \ No newline at end of file diff --git a/src/plugin/scripts/simulation/external-data/.gitkeep b/src/plugin/scripts/simulation/external-data/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/plugin/scripts/verify.js b/src/plugin/scripts/verify.js deleted file mode 100644 index 3af76260e..000000000 --- a/src/plugin/scripts/verify.js +++ /dev/null @@ -1,28 +0,0 @@ -const hre = require("hardhat"); -const fs = require('fs'); -const path = require('path'); - -async function main() { - - const deployDataPath = path.resolve(__dirname, '../../../deploys.json'); - let deploysData = JSON.parse(fs.readFileSync(deployDataPath, 'utf8')); - - const BasePluginV1Factory = deploysData.BasePluginV1Factory; - - await hre.run("verify:verify", { - address: BasePluginV1Factory, - constructorArguments: [ - deploysData.factory - ], - }); - -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); \ No newline at end of file diff --git a/src/plugin/scripts/verifyPlugin.js b/src/plugin/scripts/verifyPlugin.js deleted file mode 100644 index d817ca769..000000000 --- a/src/plugin/scripts/verifyPlugin.js +++ /dev/null @@ -1,40 +0,0 @@ -const hre = require("hardhat"); -const fs = require('fs'); -const path = require('path'); - -async function main() { - - const plugin = "0x4d57eC613d3Bc7BaC34F59177BDe63C1bAb36454"; - const pluginFactory = "0x84aF948871318c58257921d687C7f187Ffa2964e"; - const factory = "0xEdbBc263C74865e67C6b16F47740Fa3901b95Ae1"; - const pool = "0x393E45Cd7AA4FaAFb71F75b2e842a69ce0fb0273"; - const feeConfig = { - alpha1: 2900, - alpha2: 12000, - beta1: 360, - beta2: 60000, - gamma1: 59, - gamma2: 8500, - baseFee: 100 - }; - - await hre.run("verify:verify", { - address: plugin, - constructorArguments: [ - pool, - factory, - pluginFactory, - feeConfig - ], - }); - -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); \ No newline at end of file diff --git a/src/plugin/slither.config.json b/src/plugin/slither.config.json deleted file mode 100644 index c21d8ff58..000000000 --- a/src/plugin/slither.config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "filter_paths": "node_modules|test", - "solc_remaps": "@=node_modules/@" -} \ No newline at end of file diff --git a/src/plugin/test/AdaptiveFee.spec.ts b/src/plugin/test/AdaptiveFee.spec.ts deleted file mode 100644 index de94c3a72..000000000 --- a/src/plugin/test/AdaptiveFee.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { expect } from './shared/expect'; -import { ethers } from 'hardhat'; -import { AdaptiveFeeTest } from '../typechain'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import snapshotGasCost from './shared/snapshotGasCost'; - -describe('AdaptiveFee', () => { - let adaptiveFee: AdaptiveFeeTest; - - async function adaptiveFeeFixture() { - const factory = await ethers.getContractFactory('AdaptiveFeeTest'); - return (await factory.deploy()) as any as AdaptiveFeeTest; - } - - beforeEach('deploy AdaptiveFeeTest', async () => { - adaptiveFee = await loadFixture(adaptiveFeeFixture); - }); - - it('check pack and unpack of config', async () => { - const config = { - alpha1: 2900, - alpha2: 15000 - 3000, - beta1: 360, - beta2: 60000, - gamma1: 59, - gamma2: 8500, - baseFee: 100, - }; - - const result = await adaptiveFee.packAndUnpackFeeConfig(config); - expect(result.alpha1).to.be.eq(config.alpha1); - expect(result.alpha2).to.be.eq(config.alpha2); - expect(result.beta1).to.be.eq(config.beta1); - expect(result.beta2).to.be.eq(config.beta2); - expect(result.gamma1).to.be.eq(config.gamma1); - expect(result.gamma2).to.be.eq(config.gamma2); - expect(result.baseFee).to.be.eq(config.baseFee); - }); - - describe('#getFee', () => { - it('fee: 0 volatility 0 volume', async () => { - expect(await adaptiveFee.getFee(0)).to.be.eq(100); - }); - - it('fee grid snapshot', async () => { - const config = { - alpha1: 2900, - alpha2: 15000 - 3000, - beta1: 360, - beta2: 60000, - gamma1: 59, - gamma2: 8500, - baseFee: 100, - }; - - const getFee = (volatility: any) => { - let sigma1 = 0; - if (config.beta1 - volatility <= -6 * config.gamma1) sigma1 = config.alpha1; - else if (config.beta1 - volatility >= 6 * config.gamma1) sigma1 = 0; - else sigma1 = config.alpha1 / (1 + Math.exp((config.beta1 - volatility) / config.gamma1)); - - let sigma2 = 0; - if (config.beta2 - volatility <= -6 * config.gamma2) sigma2 = config.alpha2; - else if (config.beta2 - volatility >= 6 * config.gamma2) sigma2 = 0; - else sigma2 = config.alpha2 / (1 + Math.exp((config.beta2 - volatility) / config.gamma2)); - - return config.baseFee + sigma1 + sigma2; - }; - - let volats = [ - 0, 25, 50, 75, 100, 112, 125, 150, 175, 200, 250, 300, 325, 350, 359, 360, 375, 400, 500, 525, 800, 1000, 1500, 2000, 3000, 5000, 8000, 10000, - 20000, 50000, 60000, 80000, 100000, - ]; - let res = ''; - - let meanError = 0; - let maxError = 0; - let prev = 0; - for (let volatility of volats) { - let fee = getFee(volatility); - let cFee = Number((await adaptiveFee.getFee(BigInt(volatility * 15))).toString()); - expect(cFee).to.be.gte(prev); - prev = cFee; - let error = ((cFee - fee) * 100) / fee; - res += '[Volt: ' + volatility + '] Fee:' + cFee + ' Correct: ' + fee + ' Error: ' + error.toFixed(2) + '% \n'; - meanError += error; - if (Math.abs(error) > maxError) maxError = error; - } - meanError /= volats.length; - res += 'Mean error: ' + meanError.toFixed(2) + '%\n'; - res += 'Max error: ' + maxError.toFixed(2) + '%\n'; - res += '\n======================================\n'; - - expect(res).to.matchSnapshot('fee grid snapshot'); - }); - }); - - describe('#getFee gas cost [ @skip-on-coverage ]', () => { - it('gas cost of 0 volatility', async () => { - await snapshotGasCost(adaptiveFee.getGasCostOfGetFee(0)); - }); - - it('gas cost of 100 volatility', async () => { - await snapshotGasCost(adaptiveFee.getGasCostOfGetFee(100)); - }); - - it('gas cost of 2000 volatility', async () => { - await snapshotGasCost(adaptiveFee.getGasCostOfGetFee(2000)); - }); - }); -}); diff --git a/src/plugin/test/AlgebraBasePluginV1.spec.ts b/src/plugin/test/AlgebraBasePluginV1.spec.ts deleted file mode 100644 index 32545f5d8..000000000 --- a/src/plugin/test/AlgebraBasePluginV1.spec.ts +++ /dev/null @@ -1,780 +0,0 @@ -import { Wallet, ZeroAddress } from 'ethers'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import checkTimepointEquals from './shared/checkTimepointEquals'; -import { expect } from './shared/expect'; -import { TEST_POOL_START_TIME, pluginFixture } from './shared/fixtures'; -import { PLUGIN_FLAGS, encodePriceSqrt, expandTo18Decimals, getMaxTick, getMinTick } from './shared/utilities'; - -import { MockPool, MockTimeAlgebraBasePluginV1, MockTimeDSFactory, MockTimeVirtualPool } from '../typechain'; - -import snapshotGasCost from './shared/snapshotGasCost'; - -describe('AlgebraBasePluginV1', () => { - let wallet: Wallet, other: Wallet; - - let plugin: MockTimeAlgebraBasePluginV1; // modified plugin - let mockPool: MockPool; // mock of AlgebraPool - let mockPluginFactory: MockTimeDSFactory; // modified plugin factory - - let minTick = getMinTick(60); - let maxTick = getMaxTick(60); - - async function initializeAtZeroTick(pool: MockPool) { - await pool.initialize(encodePriceSqrt(1, 1)); - } - - before('prepare signers', async () => { - [wallet, other] = await (ethers as any).getSigners(); - }); - - beforeEach('deploy test AlgebraBasePluginV1', async () => { - ({ plugin, mockPool, mockPluginFactory } = await loadFixture(pluginFixture)); - }); - - describe('#Initialize', async () => { - it('cannot initialize twice', async () => { - await mockPool.setPlugin(plugin); - await initializeAtZeroTick(mockPool); - - await expect(plugin.initialize()).to.be.revertedWith('Already initialized'); - }); - - it('cannot initialize detached plugin', async () => { - await initializeAtZeroTick(mockPool); - await expect(plugin.initialize()).to.be.revertedWith('Plugin not attached'); - }); - - it('cannot initialize if pool not initialized', async () => { - await mockPool.setPlugin(plugin); - await expect(plugin.initialize()).to.be.revertedWith('Pool is not initialized'); - }); - - it('can initialize for existing pool', async () => { - await initializeAtZeroTick(mockPool); - await mockPool.setPlugin(plugin); - await plugin.initialize(); - - const timepoint = await plugin.timepoints(0); - expect(timepoint.initialized).to.be.true; - }); - - it('can not write to uninitialized oracle', async () => { - await initializeAtZeroTick(mockPool); - await mockPool.setPlugin(plugin); - await mockPool.setPluginConfig(1); // BEFORE_SWAP_FLAG - - await expect(mockPool.swapToTick(5)).to.be.revertedWith('Not initialized'); - }); - }); - - // plain tests for hooks functionality - describe('#Hooks', () => { - it('only pool can call hooks', async () => { - const errorMessage = 'Only pool can call this'; - await expect(plugin.beforeInitialize(wallet.address, 100)).to.be.revertedWith(errorMessage); - await expect(plugin.afterInitialize(wallet.address, 100, 100)).to.be.revertedWith(errorMessage); - await expect(plugin.beforeModifyPosition(wallet.address, wallet.address, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.afterModifyPosition(wallet.address, wallet.address, 100, 100, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.beforeSwap(wallet.address, wallet.address, true, 100, 100, false, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.afterSwap(wallet.address, wallet.address, true, 100, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.beforeFlash(wallet.address, wallet.address, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.afterFlash(wallet.address, wallet.address, 100, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - }); - - describe('not implemented hooks', async () => { - let defaultConfig: bigint; - - beforeEach('connect plugin to pool', async () => { - defaultConfig = await plugin.defaultPluginConfig(); - await mockPool.setPlugin(plugin); - }); - - it('resets config after beforeModifyPosition', async () => { - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.setPluginConfig(PLUGIN_FLAGS.BEFORE_POSITION_MODIFY_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.BEFORE_POSITION_MODIFY_FLAG); - await mockPool.mint(wallet.address, wallet.address, 0, 60, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - - it('resets config after afterModifyPosition', async () => { - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.setPluginConfig(PLUGIN_FLAGS.AFTER_POSITION_MODIFY_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.AFTER_POSITION_MODIFY_FLAG); - await mockPool.mint(wallet.address, wallet.address, 0, 60, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - - it('resets config after beforeFlash', async () => { - await mockPool.setPluginConfig(PLUGIN_FLAGS.BEFORE_FLASH_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.BEFORE_FLASH_FLAG); - await mockPool.flash(wallet.address, 100, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - - it('resets config after afterFlash', async () => { - await mockPool.setPluginConfig(PLUGIN_FLAGS.AFTER_FLASH_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.AFTER_FLASH_FLAG); - await mockPool.flash(wallet.address, 100, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - }); - }); - - describe('#VolatilityVolatilityOracle', () => { - beforeEach('connect plugin to pool', async () => { - await mockPool.setPlugin(plugin); - }); - - it('initializes timepoints slot', async () => { - await initializeAtZeroTick(mockPool); - checkTimepointEquals(await plugin.timepoints(0), { - initialized: true, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - tickCumulative: 0n, - }); - }); - - describe('#getTimepoints', () => { - beforeEach(async () => await initializeAtZeroTick(mockPool)); - - // zero tick - it('current tick accumulator increases by tick over time', async () => { - let { - tickCumulatives: [tickCumulative], - } = await plugin.getTimepoints([0]); - expect(tickCumulative).to.eq(0); - await plugin.advanceTime(10); - ({ - tickCumulatives: [tickCumulative], - } = await plugin.getTimepoints([0])); - expect(tickCumulative).to.eq(0); - }); - - it('current tick accumulator after single swap', async () => { - // moves to tick -1 - await mockPool.swapToTick(-1); - - await plugin.advanceTime(4); - let { - tickCumulatives: [tickCumulative], - } = await plugin.getTimepoints([0]); - expect(tickCumulative).to.eq(-4); - }); - - it('current tick accumulator after swaps', async () => { - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { - tickCumulatives: [tickCumulative0], - } = await plugin.getTimepoints([0]); - expect(tickCumulative0).to.eq(-17852); - await plugin.advanceTime(60 * 5); - await mockPool.swapToTick(-1561); - let { - tickCumulatives: [tickCumulative1], - } = await plugin.getTimepoints([0]); - expect(tickCumulative1).to.eq(-485852); - }); - }); - - it('writes an timepoint', async () => { - await initializeAtZeroTick(mockPool); - checkTimepointEquals(await plugin.timepoints(0), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - initialized: true, - }); - await plugin.advanceTime(1); - await mockPool.swapToTick(10); - checkTimepointEquals(await plugin.timepoints(1), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME + 1), - initialized: true, - }); - }); - - it('does not write an timepoint', async () => { - await initializeAtZeroTick(mockPool); - checkTimepointEquals(await plugin.timepoints(0), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - initialized: true, - }); - await plugin.advanceTime(1); - await mockPool.mint(wallet.address, wallet.address, -240, 0, 100, '0x'); - checkTimepointEquals(await plugin.timepoints(0), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - initialized: true, - }); - }); - - describe('#getSingleTimepoint', () => { - beforeEach(async () => await initializeAtZeroTick(mockPool)); - - // zero tick - it('current tick accumulator increases by tick over time', async () => { - let { tickCumulative } = await plugin.getSingleTimepoint(0); - expect(tickCumulative).to.eq(0); - await plugin.advanceTime(10); - ({ tickCumulative } = await plugin.getSingleTimepoint(0)); - expect(tickCumulative).to.eq(0); - }); - - it('current tick accumulator after single swap', async () => { - // moves to tick -1 - await mockPool.swapToTick(-1); - - await plugin.advanceTime(4); - let { tickCumulative } = await plugin.getSingleTimepoint(0); - expect(tickCumulative).to.eq(-4); - }); - - it('current tick accumulator after swaps', async () => { - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { tickCumulative: tickCumulative0 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative0).to.eq(-17852); - await plugin.advanceTime(60 * 5); - await mockPool.swapToTick(-1561); - let { tickCumulative: tickCumulative1 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative1).to.eq(-485852); - }); - }); - - describe('#prepayTimepointsStorageSlots', () => { - it('can prepay', async () => { - await plugin.prepayTimepointsStorageSlots(0, 50); - }); - - it('can prepay with space', async () => { - await plugin.prepayTimepointsStorageSlots(10, 50); - }); - - it('writes after swap, prepaid after init', async () => { - await initializeAtZeroTick(mockPool); - await plugin.prepayTimepointsStorageSlots(1, 1); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.eq(1); - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.not.eq(1); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { tickCumulative: tickCumulative0 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative0).to.eq(-17852); - }); - - it('writes after swap, prepaid before init', async () => { - await plugin.prepayTimepointsStorageSlots(0, 2); - await initializeAtZeroTick(mockPool); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.eq(1); - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.not.eq(1); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { tickCumulative: tickCumulative0 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative0).to.eq(-17852); - }); - - describe('failure cases', async () => { - it('cannot rewrite initialized slot', async () => { - await initializeAtZeroTick(mockPool); - await expect(plugin.prepayTimepointsStorageSlots(0, 2)).to.be.reverted; - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - await expect(plugin.prepayTimepointsStorageSlots(1, 2)).to.be.reverted; - await expect(plugin.prepayTimepointsStorageSlots(2, 2)).to.be.not.reverted; - }); - - it('cannot prepay 0 slots', async () => { - await expect(plugin.prepayTimepointsStorageSlots(0, 0)).to.be.revertedWithoutReason; - }); - - it('cannot overflow index', async () => { - await plugin.prepayTimepointsStorageSlots(0, 10); - expect(plugin.prepayTimepointsStorageSlots(11, 2n ** 16n - 5n)).to.be.revertedWithoutReason; - expect(plugin.prepayTimepointsStorageSlots(11, 2n ** 16n)).to.be.revertedWithoutReason; - }); - }); - }); - }); - - describe('#DynamicFeeManager', () => { - describe('#adaptiveFee', function () { - this.timeout(0); - const liquidity = expandTo18Decimals(1000); - const DAY = 60 * 60 * 24; - let mint: any; - - beforeEach('initialize pool', async () => { - await mockPool.setPlugin(plugin); - await initializeAtZeroTick(mockPool); - mint = async (recipient: string, tickLower: number, tickUpper: number, liquidityDesired: number) => { - await mockPool.mint(recipient, recipient, tickLower, tickUpper, liquidityDesired, '0x'); - }; - }); - - it('does not change at 0 volume', async () => { - await plugin.advanceTime(1); - await mockPool.mint(wallet.address, wallet.address, -6000, 6000, liquidity, '0x'); - let fee2 = (await mockPool.overrideFee()); - await plugin.advanceTime(DAY + 600); - await mint(wallet.address, -6000, 6000, 1); - let fee3 = (await mockPool.overrideFee()); - expect(fee3).to.be.equal(fee2); - }); - - it('does not change fee after first swap in block', async () => { - await mockPool.mint(wallet.address, wallet.address, -6000, 6000, liquidity, '0x'); - await plugin.advanceTime(DAY + 600); - await mockPool.swapToTick(100); - let feeInit = (await mockPool.overrideFee()); - await mockPool.swapToTick(100000); - await mockPool.swapToTick(100001); - let feeAfter = (await mockPool.overrideFee()); - expect(feeAfter).to.be.equal(feeInit); - }); - - it('does not change if alphas are zeroes', async () => { - await plugin.changeFeeConfiguration({ - alpha1: 0, - alpha2: 0, - beta1: 360, - beta2: 60000, - gamma1: 59, - gamma2: 8500, - baseFee: 100, - }); - await mockPool.mint(wallet.address, wallet.address, -6000, 6000, liquidity, '0x'); - await plugin.advanceTime(DAY + 600); - await mockPool.swapToTick(100000); - let feeInit = (await mockPool.overrideFee()); - await plugin.advanceTime(DAY + 600); - await mockPool.swapToTick(-100000); - let feeFinal = (await mockPool.overrideFee()); - expect(feeFinal).to.be.equal(feeInit); - }); - - it('single huge step after day', async () => { - await mint(wallet.address, -24000, 24000, liquidity * 1000000000n); - - await plugin.advanceTime(DAY); - await mockPool.swapToTick(10); - await plugin.advanceTime(60); - await mockPool.swapToTick(-10000); - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - - let stats = []; - const tick = 10; - for (let i = 0; i < 25; i++) { - await mockPool.swapToTick(tick - i); - let fee = (await mockPool.overrideFee()); - stats.push(`Fee: ${fee} `); - await plugin.advanceTime(60 * 60); - } - expect(stats).to.matchSnapshot('fee stats after step'); - }); - - it('single huge step after initialization', async () => { - await mint(wallet.address, -24000, 24000, liquidity * 1000000000n); - - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - await plugin.advanceTime(60); - await mockPool.swapToTick(-10000); - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - - let stats = []; - const tick = 10; - for (let i = 0; i < 25; i++) { - await mockPool.swapToTick(tick - i); - let fee = (await mockPool.overrideFee()); - stats.push(`Fee: ${fee} `); - await plugin.advanceTime(60 * 60); - } - expect(stats).to.matchSnapshot('fee stats after step'); - }); - - it('single huge spike after day', async () => { - await mint(wallet.address, -24000, 24000, liquidity * 1000000000n); - await plugin.advanceTime(DAY); - await plugin.advanceTime(60); - await mockPool.swapToTick(-10000); - await plugin.advanceTime(1); - await mockPool.swapToTick(0); - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - - let stats = []; - const tick = 10; - for (let i = 0; i < 25; i++) { - await mockPool.swapToTick(tick - i); - let fee = (await mockPool.overrideFee()); - stats.push(`Fee: ${fee} `); - await plugin.advanceTime(60 * 60); - } - expect(stats).to.matchSnapshot('fee stats after spike'); - }); - - it('single huge spike after initialization', async () => { - await mint(wallet.address, -24000, 24000, liquidity * 1000000000n); - - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - await plugin.advanceTime(60); - await mockPool.swapToTick(-10000); - await plugin.advanceTime(1); - await mockPool.swapToTick(-11); - await plugin.advanceTime(60); - await mockPool.swapToTick(0); - - let stats = []; - const tick = 0; - for (let i = 0; i < 25; i++) { - await mockPool.swapToTick(tick - i); - let fee = (await mockPool.overrideFee()); - stats.push(`Fee: ${fee} `); - await plugin.advanceTime(60 * 60); - } - expect(stats).to.matchSnapshot('fee stats after spike'); - }); - - describe('#getCurrentFee', async () => { - it('works with dynamic fee', async () => { - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - const currentFee = await plugin.getCurrentFee(); - expect(currentFee).to.be.eq(100); - }); - - it('works if alphas are zeroes', async () => { - await plugin.changeFeeConfiguration({ - alpha1: 0, - alpha2: 0, - beta1: 1001, - beta2: 1006, - gamma1: 20, - gamma2: 22, - baseFee: 100, - }); - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - await plugin.advanceTime(60); - await mockPool.swapToTick(10); - const currentFee = await plugin.getCurrentFee(); - expect(currentFee).to.be.eq(100); - }); - - it('works equal before and after timepoint write', async () => { - await plugin.advanceTime(60); - await mockPool.swapToTick(100); - await plugin.advanceTime(60 * 10); - await mockPool.swapToTick(1000); - await plugin.advanceTime(60 * 10); - const currentFee = await plugin.getCurrentFee(); - await mockPool.swapToTick(-1000); - const currentFeeAfterSwap = await plugin.getCurrentFee(); - expect(currentFeeAfterSwap).to.be.eq(currentFee); - await plugin.advanceTime(1); - const currentFee2 = await plugin.getCurrentFee(); - expect(currentFeeAfterSwap).to.be.not.eq(currentFee2); - }); - }); - }); - }); - - describe('#FarmingPlugin', () => { - describe('virtual pool tests', () => { - let virtualPoolMock: MockTimeVirtualPool; - - beforeEach('deploy virtualPoolMock', async () => { - await mockPluginFactory.setFarmingAddress(wallet); - const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; - }); - - it('set incentive works', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - }); - - it('can detach incentive', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await plugin.setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('can detach incentive even if no more has rights to connect plugins', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPluginFactory.setFarmingAddress(other); - await plugin.setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('cannot attach incentive even if no more has rights to connect plugins', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPluginFactory.setFarmingAddress(other); - await expect(plugin.setIncentive(other)).to.be.revertedWith('Not allowed to set incentive'); - }); - - it('new farming can detach old incentive', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPluginFactory.setFarmingAddress(other); - await plugin.connect(other).setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('cannot detach incentive if nothing connected', async () => { - await mockPool.setPlugin(plugin); - await expect(plugin.setIncentive(ZeroAddress)).to.be.revertedWith('Already active'); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('cannot set same incentive twice', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Already active'); - }); - - it('cannot set incentive if has active', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await expect(plugin.setIncentive(wallet.address)).to.be.revertedWith('Has active incentive'); - }); - - it('can detach incentive if not connected to pool', async () => { - const defaultConfig = await plugin.defaultPluginConfig(); - await mockPool.setPlugin(plugin); - await mockPool.setPluginConfig(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - await mockPool.setPlugin(ZeroAddress); - await plugin.setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('can set incentive if afterSwap hook is active', async () => { - const defaultConfig = await plugin.defaultPluginConfig(); - await mockPool.setPlugin(plugin); - await mockPool.setPluginConfig(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); - }); - - it('set incentive works only for PluginFactory.farmingAddress', async () => { - await mockPluginFactory.setFarmingAddress(ZeroAddress); - await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Not allowed to set incentive'); - }); - - it('incentive can not be attached if plugin is not attached', async () => { - await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Plugin not attached'); - }); - - it('incentive attached before initialization', async () => { - await mockPool.setPlugin(plugin); - - await plugin.setIncentive(virtualPoolMock); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.mint(wallet.address, wallet.address, -120, 120, 1, '0x'); - await mockPool.mint(wallet.address, wallet.address, minTick, maxTick, 1, '0x'); - - await mockPool.swapToTick(-130); - - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; - - const tick = (await mockPool.globalState()).tick; - expect(await virtualPoolMock.currentTick()).to.be.eq(tick); - expect(await virtualPoolMock.timestamp()).to.be.gt(0); - }); - - it('incentive attached after initialization', async () => { - await mockPool.setPlugin(plugin); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await plugin.setIncentive(virtualPoolMock); - - await mockPool.mint(wallet.address, wallet.address, -120, 120, 1, '0x'); - await mockPool.mint(wallet.address, wallet.address, minTick, maxTick, 1, '0x'); - - await mockPool.swapToTick(-130); - - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; - - const tick = (await mockPool.globalState()).tick; - expect(await virtualPoolMock.currentTick()).to.be.eq(tick); - expect(await virtualPoolMock.timestamp()).to.be.gt(0); - }); - - it.skip('swap with finished incentive', async () => { - /*await virtualPoolMock.setIsExist(false); - await mockPool.setIncentive(virtualPoolMock.address); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mint(wallet.address, -120, 120, 1); - await mint(wallet.address, minTick, maxTick, 1); - expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); - - await swapToLowerPrice(encodePriceSqrt(1, 2), wallet.address); - - expect(await mockPool.activeIncentive()).to.be.eq(ethers.constants.AddressZero); - expect(await virtualPoolMock.currentTick()).to.be.eq(0); - expect(await virtualPoolMock.timestamp()).to.be.eq(0); - */ - }); - - it.skip('swap with not started yet incentive', async () => { - /* - await virtualPoolMock.setIsStarted(false); - await mockPool.setIncentive(virtualPoolMock.address); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mint(wallet.address, -120, 120, 1); - await mint(wallet.address, minTick, maxTick, 1); - expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); - - await swapToLowerPrice(encodePriceSqrt(1, 2), wallet.address); - - const tick = (await mockPool.globalState()).tick; - expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); - expect(await virtualPoolMock.currentTick()).to.be.eq(tick); - expect(await virtualPoolMock.timestamp()).to.be.eq(0); - */ - }); - }); - - describe('#isIncentiveConnected', () => { - let virtualPoolMock: MockTimeVirtualPool; - - beforeEach('deploy virtualPoolMock', async () => { - await mockPluginFactory.setFarmingAddress(wallet); - const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; - }); - - it('true with active incentive', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; - }); - - it('false with invalid address', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.isIncentiveConnected(wallet.address)).to.be.false; - }); - - it('false if plugin detached', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPool.setPlugin(ZeroAddress); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.false; - }); - - it('false if hook deactivated', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPool.setPluginConfig(0); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.false; - }); - }); - - describe('#Incentive', () => { - it('incentive is not detached after swap', async () => { - await mockPool.setPlugin(plugin); - await initializeAtZeroTick(mockPool); - await mockPluginFactory.setFarmingAddress(wallet.address); - - const vpStubFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - let vpStub = (await vpStubFactory.deploy()) as any as MockTimeVirtualPool; - - await plugin.setIncentive(vpStub); - const initLiquidityAmount = 10000000000n; - await mockPool.mint(wallet.address, wallet.address, -120, 120, initLiquidityAmount, '0x'); - await mockPool.mint(wallet.address, wallet.address, -1200, 1200, initLiquidityAmount, '0x'); - await mockPool.swapToTick(-200); - - expect(await plugin.incentive()).to.be.eq(await vpStub.getAddress()); - }); - }); - }); - - describe('AlgebraBasePluginV1 external methods', () => { - describe('#changeFeeConfiguration', () => { - const configuration = { - alpha1: 3002, - alpha2: 10009, - beta1: 1001, - beta2: 1006, - gamma1: 20, - gamma2: 22, - baseFee: 150, - }; - it('fails if caller is not factory', async () => { - await expect(plugin.connect(other).changeFeeConfiguration(configuration)).to.be.reverted; - }); - - it('updates baseFeeConfiguration', async () => { - await plugin.changeFeeConfiguration(configuration); - - const newConfig = await plugin.feeConfig(); - - expect(newConfig.alpha1).to.eq(configuration.alpha1); - expect(newConfig.alpha2).to.eq(configuration.alpha2); - expect(newConfig.beta1).to.eq(configuration.beta1); - expect(newConfig.beta2).to.eq(configuration.beta2); - expect(newConfig.gamma1).to.eq(configuration.gamma1); - expect(newConfig.gamma2).to.eq(configuration.gamma2); - expect(newConfig.baseFee).to.eq(configuration.baseFee); - }); - - it('feeConfig getter gas cost [ @skip-on-coverage ]', async () => { - await plugin.changeFeeConfiguration(configuration); - await snapshotGasCost(plugin.feeConfig.estimateGas()); - }); - - it('emits event', async () => { - await expect(plugin.changeFeeConfiguration(configuration)) - .to.emit(plugin, 'FeeConfiguration') - .withArgs([...Object.values(configuration)]); - }); - - it('cannot exceed max fee', async () => { - let wrongConfig = { ...configuration }; - wrongConfig.alpha1 = 30000; - wrongConfig.alpha2 = 30000; - wrongConfig.baseFee = 15000; - await expect(plugin.changeFeeConfiguration(wrongConfig)).to.be.revertedWith('Max fee exceeded'); - }); - - it('cannot set zero gamma', async () => { - let wrongConfig1 = { ...configuration }; - wrongConfig1.gamma1 = 0; - await expect(plugin.changeFeeConfiguration(wrongConfig1)).to.be.revertedWith('Gammas must be > 0'); - - let wrongConfig2 = { ...configuration }; - wrongConfig2.gamma2 = 0; - await expect(plugin.changeFeeConfiguration(wrongConfig2)).to.be.revertedWith('Gammas must be > 0'); - }); - }); - }); -}); diff --git a/src/plugin/test/AlgebraBasePluginV2.spec.ts b/src/plugin/test/AlgebraBasePluginV2.spec.ts deleted file mode 100644 index 6c732703d..000000000 --- a/src/plugin/test/AlgebraBasePluginV2.spec.ts +++ /dev/null @@ -1,577 +0,0 @@ -import { Wallet, ZeroAddress } from 'ethers'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import checkTimepointEquals from './shared/checkTimepointEquals'; -import { expect } from './shared/expect'; -import { TEST_POOL_START_TIME, pluginFixtureV2 } from './shared/fixtures'; -import { PLUGIN_FLAGS, encodePriceSqrt, expandTo18Decimals, getMaxTick, getMinTick } from './shared/utilities'; - -import { MockPool, MockTimeAlgebraBasePluginV2, MockTimeDSFactoryV2, MockTimeVirtualPool } from '../typechain'; - -import snapshotGasCost from './shared/snapshotGasCost'; - -describe('AlgebraBasePluginV2', () => { - let wallet: Wallet, other: Wallet; - - let plugin: MockTimeAlgebraBasePluginV2; // modified plugin - let mockPool: MockPool; // mock of AlgebraPool - let mockPluginFactory: MockTimeDSFactoryV2; // modified plugin factory - - let minTick = getMinTick(60); - let maxTick = getMaxTick(60); - - async function initializeAtZeroTick(pool: MockPool) { - await pool.initialize(encodePriceSqrt(1, 1)); - } - - before('prepare signers', async () => { - [wallet, other] = await (ethers as any).getSigners(); - }); - - beforeEach('deploy test AlgebraBasePluginV2', async () => { - ({ plugin, mockPool, mockPluginFactory} = await loadFixture(pluginFixtureV2)); - }); - - describe('#Initialize', async () => { - it('cannot initialize twice', async () => { - await mockPool.setPlugin(plugin); - await initializeAtZeroTick(mockPool); - - await expect(plugin.initialize()).to.be.revertedWith('Already initialized'); - }); - - it('cannot initialize detached plugin', async () => { - await initializeAtZeroTick(mockPool); - await expect(plugin.initialize()).to.be.revertedWith('Plugin not attached'); - }); - - it('cannot initialize if pool not initialized', async () => { - await mockPool.setPlugin(plugin); - await expect(plugin.initialize()).to.be.revertedWith('Pool is not initialized'); - }); - - it('can initialize for existing pool', async () => { - await initializeAtZeroTick(mockPool); - await mockPool.setPlugin(plugin); - await plugin.initialize(); - - const timepoint = await plugin.timepoints(0); - expect(timepoint.initialized).to.be.true; - }); - - it('can not write to uninitialized oracle', async () => { - await initializeAtZeroTick(mockPool); - await mockPool.setPlugin(plugin); - await mockPool.setPluginConfig(1); // BEFORE_SWAP_FLAG - - await expect(mockPool.swapToTick(5)).to.be.revertedWith('Not initialized'); - }); - }); - - // plain tests for hooks functionality - describe('#Hooks', () => { - it('only pool can call hooks', async () => { - const errorMessage = 'Only pool can call this'; - await expect(plugin.beforeInitialize(wallet.address, 100)).to.be.revertedWith(errorMessage); - await expect(plugin.afterInitialize(wallet.address, 100, 100)).to.be.revertedWith(errorMessage); - await expect(plugin.beforeModifyPosition(wallet.address, wallet.address, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.afterModifyPosition(wallet.address, wallet.address, 100, 100, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.beforeSwap(wallet.address, wallet.address, true, 100, 100, false, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.afterSwap(wallet.address, wallet.address, true, 100, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.beforeFlash(wallet.address, wallet.address, 100, 100, '0x')).to.be.revertedWith(errorMessage); - await expect(plugin.afterFlash(wallet.address, wallet.address, 100, 100, 100, 100, '0x')).to.be.revertedWith(errorMessage); - }); - - describe('not implemented hooks', async () => { - let defaultConfig: bigint; - - beforeEach('connect plugin to pool', async () => { - defaultConfig = await plugin.defaultPluginConfig(); - await mockPool.setPlugin(plugin); - }); - - it('resets config after beforeModifyPosition', async () => { - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.setPluginConfig(PLUGIN_FLAGS.BEFORE_POSITION_MODIFY_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.BEFORE_POSITION_MODIFY_FLAG); - await mockPool.mint(wallet.address, wallet.address, 0, 60, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - - it('resets config after afterModifyPosition', async () => { - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.setPluginConfig(PLUGIN_FLAGS.AFTER_POSITION_MODIFY_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.AFTER_POSITION_MODIFY_FLAG); - await mockPool.mint(wallet.address, wallet.address, 0, 60, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - - it('resets config after beforeFlash', async () => { - await mockPool.setPluginConfig(PLUGIN_FLAGS.BEFORE_FLASH_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.BEFORE_FLASH_FLAG); - await mockPool.flash(wallet.address, 100, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - - it('resets config after afterFlash', async () => { - await mockPool.setPluginConfig(PLUGIN_FLAGS.AFTER_FLASH_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.AFTER_FLASH_FLAG); - await mockPool.flash(wallet.address, 100, 100, '0x'); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); - }); - }); - }); - - describe('#VolatilityVolatilityOracle', () => { - beforeEach('connect plugin to pool', async () => { - await mockPool.setPlugin(plugin); - }); - - it('initializes timepoints slot', async () => { - await initializeAtZeroTick(mockPool); - checkTimepointEquals(await plugin.timepoints(0), { - initialized: true, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - tickCumulative: 0n, - }); - }); - - describe('#getTimepoints', () => { - beforeEach(async () => await initializeAtZeroTick(mockPool)); - - // zero tick - it('current tick accumulator increases by tick over time', async () => { - let { - tickCumulatives: [tickCumulative], - } = await plugin.getTimepoints([0]); - expect(tickCumulative).to.eq(0); - await plugin.advanceTime(10); - ({ - tickCumulatives: [tickCumulative], - } = await plugin.getTimepoints([0])); - expect(tickCumulative).to.eq(0); - }); - - it('current tick accumulator after single swap', async () => { - // moves to tick -1 - await mockPool.swapToTick(-1); - - await plugin.advanceTime(4); - let { - tickCumulatives: [tickCumulative], - } = await plugin.getTimepoints([0]); - expect(tickCumulative).to.eq(-4); - }); - - it('current tick accumulator after swaps', async () => { - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { - tickCumulatives: [tickCumulative0], - } = await plugin.getTimepoints([0]); - expect(tickCumulative0).to.eq(-17852); - await plugin.advanceTime(60 * 5); - await mockPool.swapToTick(-1561); - let { - tickCumulatives: [tickCumulative1], - } = await plugin.getTimepoints([0]); - expect(tickCumulative1).to.eq(-485852); - }); - }); - - it('writes an timepoint', async () => { - await initializeAtZeroTick(mockPool); - checkTimepointEquals(await plugin.timepoints(0), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - initialized: true, - }); - await plugin.advanceTime(1); - await mockPool.swapToTick(10); - checkTimepointEquals(await plugin.timepoints(1), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME + 1), - initialized: true, - }); - }); - - it('does not write an timepoint', async () => { - await initializeAtZeroTick(mockPool); - checkTimepointEquals(await plugin.timepoints(0), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - initialized: true, - }); - await plugin.advanceTime(1); - await mockPool.mint(wallet.address, wallet.address, -240, 0, 100, '0x'); - checkTimepointEquals(await plugin.timepoints(0), { - tickCumulative: 0n, - blockTimestamp: BigInt(TEST_POOL_START_TIME), - initialized: true, - }); - }); - - describe('#getSingleTimepoint', () => { - beforeEach(async () => await initializeAtZeroTick(mockPool)); - - // zero tick - it('current tick accumulator increases by tick over time', async () => { - let { tickCumulative } = await plugin.getSingleTimepoint(0); - expect(tickCumulative).to.eq(0); - await plugin.advanceTime(10); - ({ tickCumulative } = await plugin.getSingleTimepoint(0)); - expect(tickCumulative).to.eq(0); - }); - - it('current tick accumulator after single swap', async () => { - // moves to tick -1 - await mockPool.swapToTick(-1); - - await plugin.advanceTime(4); - let { tickCumulative } = await plugin.getSingleTimepoint(0); - expect(tickCumulative).to.eq(-4); - }); - - it('current tick accumulator after swaps', async () => { - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { tickCumulative: tickCumulative0 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative0).to.eq(-17852); - await plugin.advanceTime(60 * 5); - await mockPool.swapToTick(-1561); - let { tickCumulative: tickCumulative1 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative1).to.eq(-485852); - }); - }); - - describe('#prepayTimepointsStorageSlots', () => { - it('can prepay', async () => { - await plugin.prepayTimepointsStorageSlots(0, 50); - }); - - it('can prepay with space', async () => { - await plugin.prepayTimepointsStorageSlots(10, 50); - }); - - it('writes after swap, prepaid after init', async () => { - await initializeAtZeroTick(mockPool); - await plugin.prepayTimepointsStorageSlots(1, 1); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.eq(1); - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.not.eq(1); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { tickCumulative: tickCumulative0 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative0).to.eq(-17852); - }); - - it('writes after swap, prepaid before init', async () => { - await plugin.prepayTimepointsStorageSlots(0, 2); - await initializeAtZeroTick(mockPool); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.eq(1); - await mockPool.swapToTick(-4463); - expect((await mockPool.globalState()).tick).to.eq(-4463); - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - expect((await plugin.timepoints(1)).blockTimestamp).to.be.not.eq(1); - expect((await mockPool.globalState()).tick).to.eq(-1560); - let { tickCumulative: tickCumulative0 } = await plugin.getSingleTimepoint(0); - expect(tickCumulative0).to.eq(-17852); - }); - - describe('failure cases', async () => { - it('cannot rewrite initialized slot', async () => { - await initializeAtZeroTick(mockPool); - await expect(plugin.prepayTimepointsStorageSlots(0, 2)).to.be.reverted; - await plugin.advanceTime(4); - await mockPool.swapToTick(-1560); - await expect(plugin.prepayTimepointsStorageSlots(1, 2)).to.be.reverted; - await expect(plugin.prepayTimepointsStorageSlots(2, 2)).to.be.not.reverted; - }); - - it('cannot prepay 0 slots', async () => { - await expect(plugin.prepayTimepointsStorageSlots(0, 0)).to.be.revertedWithoutReason; - }); - - it('cannot overflow index', async () => { - await plugin.prepayTimepointsStorageSlots(0, 10); - expect(plugin.prepayTimepointsStorageSlots(11, 2n ** 16n - 5n)).to.be.revertedWithoutReason; - expect(plugin.prepayTimepointsStorageSlots(11, 2n ** 16n)).to.be.revertedWithoutReason; - }); - }); - }); - }); - - describe('#SlidingFee', () => { - - beforeEach('initialize pool', async () => { - await mockPool.setPlugin(plugin); - await initializeAtZeroTick(mockPool); - }); - - describe('#setPriceChangeFactor', () => { - it('works correct', async () => { - await plugin.setPriceChangeFactor(1500) - let factor = await plugin.s_priceChangeFactor() - expect(factor).to.be.equal(1500); - }); - - it('emit event', async () => { - await expect(plugin.setPriceChangeFactor(1500)).to.emit(plugin, "PriceChangeFactor"); - }); - - it('fails if caller is not owner or manager', async () => { - await expect(plugin.connect(other).setPriceChangeFactor(1500)).to.be.reverted; - }); - }) - - describe('#setBaseFee', () => { - it('works correct', async () => { - await plugin.setBaseFee(1500) - let baseFee = await plugin.s_baseFee() - expect(baseFee).to.be.equal(1500); - }); - - it('emit event', async () => { - await expect(plugin.setBaseFee(1500)).to.emit(plugin, "BaseFee"); - }); - - it('fails if caller is not owner or manager', async () => { - await expect(plugin.connect(other).setBaseFee(1500)).to.be.reverted; - }); - }) - }) - - describe('#FarmingPlugin', () => { - describe('virtual pool tests', () => { - let virtualPoolMock: MockTimeVirtualPool; - - beforeEach('deploy virtualPoolMock', async () => { - await mockPluginFactory.setFarmingAddress(wallet); - const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; - }); - - it('returns pool address', async () => { - expect(await plugin.getPool()).to.be.eq(mockPool); - }); - - it('set incentive works', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - }); - - it('can detach incentive', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await plugin.setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('can detach incentive even if no more has rights to connect plugins', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPluginFactory.setFarmingAddress(other); - await plugin.setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('cannot attach incentive even if no more has rights to connect plugins', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPluginFactory.setFarmingAddress(other); - await expect(plugin.setIncentive(other)).to.be.revertedWith('Not allowed to set incentive'); - }); - - it('new farming can detach old incentive', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPluginFactory.setFarmingAddress(other); - await plugin.connect(other).setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('cannot detach incentive if nothing connected', async () => { - await mockPool.setPlugin(plugin); - await expect(plugin.setIncentive(ZeroAddress)).to.be.revertedWith('Already active'); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('cannot set same incentive twice', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Already active'); - }); - - it('cannot set incentive if has active', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await expect(plugin.setIncentive(wallet.address)).to.be.revertedWith('Has active incentive'); - }); - - it('can detach incentive if not connected to pool', async () => { - const defaultConfig = await plugin.defaultPluginConfig(); - await mockPool.setPlugin(plugin); - await mockPool.setPluginConfig(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - await mockPool.setPlugin(ZeroAddress); - await plugin.setIncentive(ZeroAddress); - expect(await plugin.incentive()).to.be.eq(ZeroAddress); - }); - - it('can set incentive if afterSwap hook is active', async () => { - const defaultConfig = await plugin.defaultPluginConfig(); - await mockPool.setPlugin(plugin); - await mockPool.setPluginConfig(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); - }); - - it('set incentive works only for PluginFactory.farmingAddress', async () => { - await mockPluginFactory.setFarmingAddress(ZeroAddress); - await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Not allowed to set incentive'); - }); - - it('incentive can not be attached if plugin is not attached', async () => { - await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Plugin not attached'); - }); - - it('incentive attached before initialization', async () => { - await mockPool.setPlugin(plugin); - - await plugin.setIncentive(virtualPoolMock); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.mint(wallet.address, wallet.address, -120, 120, 1, '0x'); - await mockPool.mint(wallet.address, wallet.address, minTick, maxTick, 1, '0x'); - - await mockPool.swapToTick(-130); - - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; - - const tick = (await mockPool.globalState()).tick; - expect(await virtualPoolMock.currentTick()).to.be.eq(tick); - expect(await virtualPoolMock.timestamp()).to.be.gt(0); - }); - - it('incentive attached after initialization', async () => { - await mockPool.setPlugin(plugin); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await plugin.setIncentive(virtualPoolMock); - - await mockPool.mint(wallet.address, wallet.address, -120, 120, 1, '0x'); - await mockPool.mint(wallet.address, wallet.address, minTick, maxTick, 1, '0x'); - - await mockPool.swapToTick(-130); - - expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; - - const tick = (await mockPool.globalState()).tick; - expect(await virtualPoolMock.currentTick()).to.be.eq(tick); - expect(await virtualPoolMock.timestamp()).to.be.gt(0); - }); - - it.skip('swap with finished incentive', async () => { - /*await virtualPoolMock.setIsExist(false); - await mockPool.setIncentive(virtualPoolMock.address); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mint(wallet.address, -120, 120, 1); - await mint(wallet.address, minTick, maxTick, 1); - expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); - - await swapToLowerPrice(encodePriceSqrt(1, 2), wallet.address); - - expect(await mockPool.activeIncentive()).to.be.eq(ethers.constants.AddressZero); - expect(await virtualPoolMock.currentTick()).to.be.eq(0); - expect(await virtualPoolMock.timestamp()).to.be.eq(0); - */ - }); - - it.skip('swap with not started yet incentive', async () => { - /* - await virtualPoolMock.setIsStarted(false); - await mockPool.setIncentive(virtualPoolMock.address); - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mint(wallet.address, -120, 120, 1); - await mint(wallet.address, minTick, maxTick, 1); - expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); - - await swapToLowerPrice(encodePriceSqrt(1, 2), wallet.address); - - const tick = (await mockPool.globalState()).tick; - expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); - expect(await virtualPoolMock.currentTick()).to.be.eq(tick); - expect(await virtualPoolMock.timestamp()).to.be.eq(0); - */ - }); - }); - - describe('#isIncentiveConnected', () => { - let virtualPoolMock: MockTimeVirtualPool; - - beforeEach('deploy virtualPoolMock', async () => { - await mockPluginFactory.setFarmingAddress(wallet); - const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; - }); - - it('true with active incentive', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; - }); - - it('false with invalid address', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - expect(await plugin.isIncentiveConnected(wallet.address)).to.be.false; - }); - - it('false if plugin detached', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPool.setPlugin(ZeroAddress); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.false; - }); - - it('false if hook deactivated', async () => { - await mockPool.setPlugin(plugin); - await plugin.setIncentive(virtualPoolMock); - await mockPool.setPluginConfig(0); - expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.false; - }); - }); - - describe('#Incentive', () => { - it('incentive is not detached after swap', async () => { - await mockPool.setPlugin(plugin); - await initializeAtZeroTick(mockPool); - await mockPluginFactory.setFarmingAddress(wallet.address); - - const vpStubFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - let vpStub = (await vpStubFactory.deploy()) as any as MockTimeVirtualPool; - - await plugin.setIncentive(vpStub); - const initLiquidityAmount = 10000000000n; - await mockPool.mint(wallet.address, wallet.address, -120, 120, initLiquidityAmount, '0x'); - await mockPool.mint(wallet.address, wallet.address, -1200, 1200, initLiquidityAmount, '0x'); - await mockPool.swapToTick(-200); - - expect(await plugin.incentive()).to.be.eq(await vpStub.getAddress()); - }); - }); - }); - -}); diff --git a/src/plugin/test/AlgebraOracleV1TWAP.spec.ts b/src/plugin/test/AlgebraOracleV1TWAP.spec.ts deleted file mode 100644 index 40c9478d2..000000000 --- a/src/plugin/test/AlgebraOracleV1TWAP.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { ContractFactory } from 'ethers'; -import { AlgebraOracleV1TWAP, MockPool, MockTimeDSFactory, TestERC20 } from '../typechain'; -import { tokensFixture } from './shared/externalFixtures'; -import { ZERO_ADDRESS } from './shared/fixtures'; - -describe('AlgebraOracleV1TWAP', () => { - let tokens: TestERC20[]; - let algebraOracleV1TWAP: AlgebraOracleV1TWAP; - let mockPluginFactory: MockTimeDSFactory; - - const algebraOracleV1TWAPFixture = async () => { - const tokensFixtureRes = await tokensFixture(); - tokens = [tokensFixtureRes.token0, tokensFixtureRes.token1]; - - const mockPluginFactoryFactory = await ethers.getContractFactory('MockTimeDSFactory'); - const _mockPluginFactory = await mockPluginFactoryFactory.deploy(ZERO_ADDRESS); - - const algebraOracleV1TWAPFactory = await ethers.getContractFactory('AlgebraOracleV1TWAP'); - const _algebraOracleV1TWAP = await algebraOracleV1TWAPFactory.deploy(_mockPluginFactory); - - return { - tokens: tokens as TestERC20[], - algebraOracleV1TWAP: _algebraOracleV1TWAP as any as AlgebraOracleV1TWAP, - mockPluginFactory: _mockPluginFactory as any as MockTimeDSFactory, - }; - }; - - beforeEach('deploy fixture', async () => { - const fixtures = await loadFixture(algebraOracleV1TWAPFixture); - tokens = fixtures.tokens; - algebraOracleV1TWAP = fixtures.algebraOracleV1TWAP; - mockPluginFactory = fixtures.mockPluginFactory; - }); - - it('has correct pluginFactory', async () => { - expect(await algebraOracleV1TWAP.pluginFactory()).to.be.eq(await mockPluginFactory.getAddress()); - }); - - describe('#getAverageTick', () => { - let mockVolatilityOracleFactory: ContractFactory; - let mockPool: MockPool; - - beforeEach('create mockVolatilityOracleFactory', async () => { - mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockPoolFactory = await ethers.getContractFactory('MockPool'); - mockPool = (await mockPoolFactory.deploy()) as any as MockPool; - }); - - it('reverts if oracle not exist', async () => { - await expect(algebraOracleV1TWAP.getAverageTick(ZERO_ADDRESS, 0)).to.be.revertedWith('Oracle does not exist'); - }); - - describe('plugin connected', async () => { - it('correct output when tick is 0', async () => { - const period = 3; - const tickCumulatives = [12n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockPool, mockVolatilityOracle); - - await mockPool.setPlugin(mockVolatilityOracle); - await mockPool.setPluginConfig(1); - - const [oracleLibraryTick, isConnected] = await algebraOracleV1TWAP.getAverageTick(mockPool, period); - - expect(oracleLibraryTick).to.equal(0n); - expect(isConnected).to.be.true; - }); - - it('correct output for positive tick', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockPool, mockVolatilityOracle); - await mockPool.setPlugin(mockVolatilityOracle); - await mockPool.setPluginConfig(1); - - const [oracleLibraryTick, isConnected] = await algebraOracleV1TWAP.getAverageTick(mockPool, period); - - // Always round to negative infinity - // In this case, we don't have do anything - expect(oracleLibraryTick).to.equal(1n); - expect(isConnected).to.be.true; - }); - }); - - describe('plugin not connected', async () => { - it('correct output when tick is 0', async () => { - const period = 3; - const tickCumulatives = [12n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockPool, mockVolatilityOracle); - - const [oracleLibraryTick, isConnected] = await algebraOracleV1TWAP.getAverageTick(mockPool, period); - - expect(oracleLibraryTick).to.equal(0n); - expect(isConnected).to.be.false; - }); - - it('correct output for positive tick', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockPool, mockVolatilityOracle); - - const [oracleLibraryTick, isConnected] = await algebraOracleV1TWAP.getAverageTick(mockPool, period); - - // Always round to negative infinity - // In this case, we don't have do anything - expect(oracleLibraryTick).to.equal(1n); - expect(isConnected).to.be.false; - }); - }); - }); - - describe('#getQuoteAtTick', () => { - it('token0: returns correct value when at min tick | 0 < sqrtRatioX96 <= type(uint128).max', async () => { - const quoteAmount = await algebraOracleV1TWAP.getQuoteAtTick(-887272n, 2n ** 128n - 1n, tokens[0], tokens[1]); - expect(quoteAmount).to.equal(1n); - }); - }); - - describe('#latestTimestamp', () => { - it('returns correct value', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockVolatilityOracle, mockVolatilityOracle); - - const latestTimestamp = await algebraOracleV1TWAP.latestTimestamp(mockVolatilityOracle); - expect(latestTimestamp).to.equal(101); - }); - }); - - describe('#oldestTimestamp', () => { - it('returns correct value without overflow', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 1], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockVolatilityOracle, mockVolatilityOracle); - - const oldestTimestamp = await algebraOracleV1TWAP.oldestTimestamp(mockVolatilityOracle); - expect(oldestTimestamp).to.be.eq(period); - }); - - it('returns correct value with overflow', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 2], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockVolatilityOracle, mockVolatilityOracle); - - await mockVolatilityOracle.setTimepoint(2, true, 1000, 10, 20); - const oldestTimestamp = await algebraOracleV1TWAP.oldestTimestamp(mockVolatilityOracle); - expect(oldestTimestamp).to.be.eq(1000); - }); - }); - - describe('#latestIndex', () => { - it('returns correct value', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockVolatilityOracle, mockVolatilityOracle); - - const latestIndex = await algebraOracleV1TWAP.latestIndex(mockVolatilityOracle); - expect(latestIndex).to.equal(1); - }); - }); - - describe('#isOracleConnected', () => { - it('returns correct value', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - - const mockPoolFactory = await ethers.getContractFactory('MockPool'); - const mockPool = await mockPoolFactory.deploy(); - - await mockPluginFactory.setPluginForPool(mockPool, mockVolatilityOracle); - - expect(await algebraOracleV1TWAP.isOracleConnected(mockPool)).to.be.false; - - await mockPool.setPlugin(mockVolatilityOracle); - expect(await algebraOracleV1TWAP.isOracleConnected(mockPool)).to.be.false; - - await mockPool.setPluginConfig(1); - expect(await algebraOracleV1TWAP.isOracleConnected(mockPool)).to.be.true; - }); - }); - - describe('#oldestIndex', () => { - it('returns correct value without overflow', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockVolatilityOracle, mockVolatilityOracle); - - const oldestIndex = await algebraOracleV1TWAP.oldestIndex(mockVolatilityOracle); - expect(oldestIndex).to.be.eq(0); - }); - - it('returns correct value with overflow', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - await mockPluginFactory.setPluginForPool(mockVolatilityOracle, mockVolatilityOracle); - - await mockVolatilityOracle.setTimepoint(2, true, 1, 10, 20); - const oldestTimestamp = await algebraOracleV1TWAP.oldestIndex(mockVolatilityOracle); - expect(oldestTimestamp).to.be.eq(2); - }); - }); -}); diff --git a/src/plugin/test/AlgebraPool.gas.spec.ts b/src/plugin/test/AlgebraPool.gas.spec.ts deleted file mode 100644 index 732adf045..000000000 --- a/src/plugin/test/AlgebraPool.gas.spec.ts +++ /dev/null @@ -1,527 +0,0 @@ -import { ethers } from 'hardhat'; -import { Wallet } from 'ethers'; -import { loadFixture, reset as resetNetwork } from '@nomicfoundation/hardhat-network-helpers'; -import { MockTimeAlgebraPool } from '../../core/typechain'; -import { MockTimeAlgebraBasePluginV1, MockTimeDSFactory, MockTimeVirtualPool } from '../typechain'; -import { expect } from './shared/expect'; - -import { algebraPoolDeployerMockFixture } from './shared/externalFixtures'; - -import snapshotGasCost from './shared/snapshotGasCost'; - -import { - expandTo18Decimals, - FeeAmount, - getMinTick, - encodePriceSqrt, - TICK_SPACINGS, - createPoolFunctions, - SwapFunction, - MintFunction, - getMaxTick, - MaxUint128, - SwapToPriceFunction, -} from '../../core/test/shared/utilities'; -import { ZERO_ADDRESS } from './shared/fixtures'; - -describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { - let wallet: Wallet, other: Wallet; - - before('create fixture loader', async () => { - await resetNetwork(); - [wallet, other] = await (ethers as any).getSigners(); - }); - - const startingPrice = encodePriceSqrt(100001, 100000); - const startingTick = 0; - const tickSpacing = TICK_SPACINGS[FeeAmount.MEDIUM]; - const minTick = getMinTick(tickSpacing); - const maxTick = getMaxTick(tickSpacing); - - async function gasTestFixture() { - const fix = await algebraPoolDeployerMockFixture(); - const pool = await fix.createPool(); - - const mockPluginFactoryFactory = await ethers.getContractFactory('MockTimeDSFactory'); - const mockPluginFactory = (await mockPluginFactoryFactory.deploy(fix.factory)) as any as MockTimeDSFactory; - - await mockPluginFactory.beforeCreatePoolHook(pool, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x'); - const pluginAddress = await mockPluginFactory.pluginByPool(pool); - - const mockDSOperatorFactory = await ethers.getContractFactory('MockTimeAlgebraBasePluginV1'); - const plugin = mockDSOperatorFactory.attach(pluginAddress) as any as MockTimeAlgebraBasePluginV1; - - await pool.setPlugin(plugin); - - const advanceTime = async (secs: any) => { - await pool.advanceTime(secs); - await plugin.advanceTime(secs); - }; - - const { swapExact0For1, swapExact1For0, swapToHigherPrice, mint, swapToLowerPrice } = createPoolFunctions({ - swapTarget: fix.swapTargetCallee, - token0: fix.token0, - token1: fix.token1, - pool, - }); - - const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); - const virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; - - await pool.initialize(encodePriceSqrt(1, 1)); - await pool.setCommunityVault(wallet.address); - - await advanceTime(1); - await mint(wallet.address, minTick, maxTick, expandTo18Decimals(2)); - await swapExact0For1(expandTo18Decimals(1), wallet.address); - await advanceTime(1); - await swapToHigherPrice(startingPrice, wallet.address); - await advanceTime(1); - expect((await pool.globalState()).tick).to.eq(startingTick); - expect((await pool.globalState()).price).to.eq(startingPrice); - - return { - advanceTime, - pool, - plugin, - virtualPoolMock, - mockPluginFactory, - swapExact0For1, - swapExact1For0, - mint, - swapToHigherPrice, - swapToLowerPrice, - }; - } - - let swapExact0For1: SwapFunction; - let swapExact1For0: SwapFunction; - let swapToHigherPrice: SwapToPriceFunction; - let pool: MockTimeAlgebraPool; - let plugin: MockTimeAlgebraBasePluginV1; - let virtualPoolMock: MockTimeVirtualPool; - let mockPluginFactory: MockTimeDSFactory; - let mint: MintFunction; - let advanceTime: any; - - for (const communityFee of [0, 60]) { - describe(communityFee > 0 ? 'fee is on' : 'fee is off', () => { - const gasTestCommunityFeeFixture = async () => { - const fix = await gasTestFixture(); - if (communityFee > 0) { - await fix.pool.setCommunityFee(communityFee); - await fix.swapExact0For1(expandTo18Decimals(1), wallet.address); - await fix.swapToHigherPrice(startingPrice, wallet.address); - await fix.advanceTime(1); - } - await fix.advanceTime(1); - await fix.swapExact0For1(1, wallet.address); - await fix.advanceTime(1); - expect((await fix.pool.globalState()).tick).to.eq(startingTick); - return fix; - }; - - beforeEach('load the fixture', async () => { - ({ advanceTime, swapExact0For1, swapExact1For0, pool, plugin, virtualPoolMock, mockPluginFactory, mint, swapToHigherPrice } = - await loadFixture(gasTestCommunityFeeFixture)); - }); - - describe('#swap', async () => { - describe('#swapExact1For0', () => { - it('first swap in block with no tick movement', async () => { - await snapshotGasCost(swapExact1For0(2000, wallet.address)); - expect((await pool.globalState()).price).to.not.eq(startingPrice); - expect((await pool.globalState()).tick).to.eq(startingTick); - }); - - it('first swap in block moves tick, no initialized crossings', async () => { - await snapshotGasCost(swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick + 1); - }); - - it('second swap in block with no tick movement', async () => { - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - expect((await pool.globalState()).tick).to.eq(startingTick + 1); - await snapshotGasCost(swapExact1For0(2000, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick + 1); - }); - }); - - describe('#swapExact0For1', () => { - it('first swap in block with no tick movement', async () => { - await snapshotGasCost(swapExact0For1(2000, wallet.address)); - expect((await pool.globalState()).price).to.not.eq(startingPrice); - expect((await pool.globalState()).tick).to.eq(startingTick); - }); - - it('first swap in block with no tick movement, static fee', async () => { - await plugin.changeFeeConfiguration({ - alpha1: 0, - alpha2: 0, - beta1: 0, - beta2: 0, - gamma1: 1, - gamma2: 1, - baseFee: 100, - }); - await snapshotGasCost(swapExact0For1(2000, wallet.address)); - expect((await pool.globalState()).price).to.not.eq(startingPrice); - expect((await pool.globalState()).tick).to.eq(startingTick); - }); - - it('first swap in block moves tick, no initialized crossings', async () => { - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick - 1); - }); - - it('second swap in block with no tick movement', async () => { - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - expect((await pool.globalState()).tick).to.eq(startingTick - 1); - await snapshotGasCost(swapExact0For1(2000, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick - 1); - }); - - it('second swap in block moves tick, no initialized crossings', async () => { - await swapExact0For1(1000, wallet.address); - expect((await pool.globalState()).tick).to.eq(startingTick); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick - 1); - }); - - it('first swap in block, large swap, no initialized crossings', async () => { - await snapshotGasCost(swapExact0For1(expandTo18Decimals(10), wallet.address)); - expect((await pool.globalState()).tick).to.eq(-35586); - }); - - it('first swap in block, large swap crossing several initialized ticks', async () => { - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - expect((await pool.globalState()).tick).to.eq(startingTick); - await advanceTime(15); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 4 * tickSpacing); // we crossed the last tick - }); - - it('several large swaps with pauses', async () => { - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - expect((await pool.globalState()).tick).to.eq(startingTick); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60 * 60); - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60 * 60); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(5 * 60 * 60); - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(19 * 60 * 60); - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 4 * tickSpacing); // we crossed the last tick - }); - - it('small swap after several large swaps with pauses', async () => { - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - expect((await pool.globalState()).tick).to.eq(startingTick); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60 * 60); - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60 * 60); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(5 * 60 * 60); - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(19 * 60 * 60); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await advanceTime(60); - await snapshotGasCost(swapExact0For1(1000, wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(0); - }); - - it('first swap in block, large swap crossing a single initialized tick', async () => { - await mint(wallet.address, minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await advanceTime(15); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 2 * tickSpacing); // we crossed the last tick - }); - - it('second swap in block, large swap crossing several initialized ticks', async () => { - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 4 * tickSpacing); - }); - - it('second swap in block, large swap crossing a single initialized tick', async () => { - await mint(wallet.address, minTick, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await swapExact0For1(expandTo18Decimals(1) / 10000n, wallet.address); - expect((await pool.globalState()).tick).to.be.gt(startingTick - 2 * tickSpacing); // we didn't cross the initialized tick - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 2 * tickSpacing); // we crossed the last tick - }); - - it('large swap crossing several initialized ticks after some time passes', async () => { - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await swapExact0For1(2, wallet.address); - await advanceTime(1); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 4 * tickSpacing); - }); - - it('large swap crossing several initialized ticks second time after some time passes', async () => { - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await swapExact0For1(expandTo18Decimals(1), wallet.address); - await swapToHigherPrice(startingPrice, wallet.address); - await advanceTime(1); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(tickSpacing * -4); - }); - }); - - describe('farming connected', async () => { - beforeEach('connect virtual pool', async () => { - await mockPluginFactory.setFarmingAddress(wallet); - await plugin.connect(wallet).setIncentive(virtualPoolMock); - }); - - it('first swap in block with no tick movement', async () => { - await snapshotGasCost(swapExact1For0(2000, wallet.address)); - expect((await pool.globalState()).price).to.not.eq(startingPrice); - expect((await pool.globalState()).tick).to.eq(startingTick); - }); - - it('first swap in block moves tick, no initialized crossings', async () => { - await snapshotGasCost(swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick + 1); - }); - - it('second swap in block with no tick movement', async () => { - await swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address); - expect((await pool.globalState()).tick).to.eq(startingTick + 1); - await snapshotGasCost(swapExact1For0(2000, wallet.address)); - expect((await pool.globalState()).tick).to.eq(startingTick + 1); - }); - }); - }); - }); - } - - describe('Positions', function () { - beforeEach('load inner fixture', async () => { - ({ advanceTime, swapExact0For1, swapExact1For0, pool, plugin, virtualPoolMock, mockPluginFactory, mint, swapToHigherPrice } = await loadFixture( - gasTestFixture - )); - }); - - describe('#mint', () => { - for (const { description, bottomTick, topTick } of [ - { - description: 'around current price', - bottomTick: startingTick - tickSpacing, - topTick: startingTick + tickSpacing, - }, - { - description: 'below current price', - bottomTick: startingTick - 2 * tickSpacing, - topTick: startingTick - tickSpacing, - }, - { - description: 'above current price', - bottomTick: startingTick + tickSpacing, - topTick: startingTick + 2 * tickSpacing, - }, - ]) { - describe(description, () => { - it('new position mint first in range', async () => { - await advanceTime(15); - await snapshotGasCost(mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1))); - }); - it('add to position existing', async () => { - await mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1)); - await advanceTime(15); - await snapshotGasCost(mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1))); - }); - it('second position in same range', async () => { - await mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1)); - await advanceTime(15); - await snapshotGasCost(mint(other.address, bottomTick, topTick, expandTo18Decimals(1))); - }); - }); - } - }); - - describe('#burn', () => { - for (const { description, bottomTick, topTick } of [ - { - description: 'around current price', - bottomTick: startingTick - tickSpacing, - topTick: startingTick + tickSpacing, - }, - { - description: 'below current price', - bottomTick: startingTick - 2 * tickSpacing, - topTick: startingTick - tickSpacing, - }, - { - description: 'above current price', - bottomTick: startingTick + tickSpacing, - topTick: startingTick + 2 * tickSpacing, - }, - ]) { - describe(description, () => { - const liquidityAmount = expandTo18Decimals(1); - beforeEach('mint a position', async () => { - await mint(wallet.address, bottomTick, topTick, liquidityAmount); - await advanceTime(15); - }); - - it('burn when only position using ticks', async () => { - await snapshotGasCost(pool.burn(bottomTick, topTick, expandTo18Decimals(1), '0x')); - }); - it('partial position burn', async () => { - await snapshotGasCost(pool.burn(bottomTick, topTick, expandTo18Decimals(1) / 2n, '0x')); - }); - it('entire position burn but other positions are using the ticks', async () => { - await mint(other.address, bottomTick, topTick, expandTo18Decimals(1)); - await advanceTime(15); - await snapshotGasCost(pool.burn(bottomTick, topTick, expandTo18Decimals(1), '0x')); - }); - }); - } - }); - - describe('#poke', () => { - const bottomTick = startingTick - tickSpacing; - const topTick = startingTick + tickSpacing; - - it('best case', async () => { - await mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1)); - await swapExact0For1(expandTo18Decimals(1) / 100n, wallet.address); - await pool.burn(bottomTick, topTick, 0, '0x'); - await swapExact0For1(expandTo18Decimals(1) / 100n, wallet.address); - await advanceTime(15); - await snapshotGasCost(pool.burn(bottomTick, topTick, 0, '0x')); - }); - }); - - describe('#collect', () => { - const bottomTick = startingTick - tickSpacing; - const topTick = startingTick + tickSpacing; - - it('close to worst case', async () => { - await mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1)); - await swapExact0For1(expandTo18Decimals(1) / 100n, wallet.address); - await pool.burn(bottomTick, topTick, 0, '0x'); // poke to accumulate fees - await advanceTime(15); - await snapshotGasCost(pool.collect(wallet.address, bottomTick, topTick, MaxUint128, MaxUint128)); - }); - - it('close to worst case, two tokens', async () => { - await mint(wallet.address, bottomTick, topTick, expandTo18Decimals(1)); - await swapExact0For1(expandTo18Decimals(1) / 100n, wallet.address); - await swapExact1For0(expandTo18Decimals(1) / 100n, wallet.address); - await pool.burn(bottomTick, topTick, 0, '0x'); // poke to accumulate fees - await advanceTime(15); - await snapshotGasCost(pool.collect(wallet.address, bottomTick, topTick, MaxUint128, MaxUint128)); - }); - }); - }); - - describe('Filled VolatilityOracle', function () { - this.timeout(600_000); - - const filledStorageFixture = async () => { - const fix = await gasTestFixture(); - await fix.advanceTime(15); - await fix.mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await fix.mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await fix.swapExact0For1(1, wallet.address); - expect((await fix.pool.globalState()).tick).to.eq(startingTick); - - const BATCH_SIZE = 300; - let summaryTimeDelta = 0; - for (let i = 0; i < 1500; i += BATCH_SIZE) { - const batch = []; - for (let j = 0; j < BATCH_SIZE; j++) { - const timeDelta = (i + j) % 2 == 0 ? 60 : 90; - summaryTimeDelta += timeDelta; - batch.push({ - advanceTimeBy: timeDelta, - tick: startingTick + i - j, - }); - } - await fix.plugin.batchUpdate(batch); - } - await fix.pool.advanceTime(summaryTimeDelta); - - return fix; - }; - - beforeEach('load inner fixture', async () => { - ({ advanceTime, swapExact0For1, swapExact1For0, pool, plugin, virtualPoolMock, mockPluginFactory, mint, swapToHigherPrice } = await loadFixture( - filledStorageFixture - )); - }); - - describe('swaps', async () => { - for (const isDynamicFee of [true, false]) { - describe(isDynamicFee ? 'dynamic fee' : 'static fee', async () => { - beforeEach(async () => { - if (!isDynamicFee) { - await plugin.changeFeeConfiguration({ - alpha1: 0, - alpha2: 0, - beta1: 0, - beta2: 0, - gamma1: 1, - gamma2: 1, - baseFee: 100, - }); - await advanceTime(15); - await swapExact0For1(1000, wallet.address); - } - }); - - it('small swap with filled volatilityOracle', async () => { - await advanceTime(15); - await snapshotGasCost(swapExact0For1(1000, wallet.address)); - }); - - it('small swap with filled volatilityOracle after 4h', async () => { - await advanceTime(4 * 60 * 60); - await snapshotGasCost(swapExact0For1(1000, wallet.address)); - }); - - it('small swap with filled volatilityOracle after 8h', async () => { - await advanceTime(8 * 60 * 60); - await snapshotGasCost(swapExact0For1(1000, wallet.address)); - }); - - it('small swap with filled volatilityOracle after 24h', async () => { - await advanceTime(24 * 60 * 60 - 1); - await swapExact0For1(1, wallet.address); - await advanceTime(5); - await snapshotGasCost(swapExact0For1(1000, wallet.address)); - }); - - it('large swap crossing several initialized ticks', async () => { - await advanceTime(15); - await mint(wallet.address, startingTick - 3 * tickSpacing, startingTick - tickSpacing, expandTo18Decimals(1)); - await mint(wallet.address, startingTick - 4 * tickSpacing, startingTick - 2 * tickSpacing, expandTo18Decimals(1)); - await swapExact0For1(2, wallet.address); - await advanceTime(15); - await snapshotGasCost(swapExact0For1(expandTo18Decimals(1), wallet.address)); - expect((await pool.globalState()).tick).to.be.lt(startingTick - 4 * tickSpacing); - }); - }); - } - }); - }); -}); diff --git a/src/plugin/test/BasePluginV1Factory.spec.ts b/src/plugin/test/BasePluginV1Factory.spec.ts deleted file mode 100644 index 38d052fda..000000000 --- a/src/plugin/test/BasePluginV1Factory.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { expect } from './shared/expect'; -import { ZERO_ADDRESS, pluginFactoryFixture } from './shared/fixtures'; - -import { BasePluginV1Factory, AlgebraBasePluginV1, MockFactory } from '../typechain'; - -describe('BasePluginV1Factory', () => { - let wallet: Wallet, other: Wallet; - - let pluginFactory: BasePluginV1Factory; - let mockAlgebraFactory: MockFactory; - - before('prepare signers', async () => { - [wallet, other] = await (ethers as any).getSigners(); - }); - - beforeEach('deploy test volatilityOracle', async () => { - ({ pluginFactory, mockFactory: mockAlgebraFactory } = await loadFixture(pluginFactoryFixture)); - }); - - describe('#Create plugin', () => { - it('only factory', async () => { - expect(pluginFactory.beforeCreatePoolHook(wallet.address, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x')).to.be - .revertedWithoutReason; - }); - - it('factory can create plugin', async () => { - const pluginFactoryFactory = await ethers.getContractFactory('BasePluginV1Factory'); - const pluginFactoryMock = (await pluginFactoryFactory.deploy(wallet.address)) as any as BasePluginV1Factory; - - const pluginAddress = await pluginFactoryMock.beforeCreatePoolHook.staticCall( - wallet.address, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS, - '0x' - ); - await pluginFactoryMock.beforeCreatePoolHook(wallet.address, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x'); - - const pluginMock = (await ethers.getContractFactory('AlgebraBasePluginV1')).attach(pluginAddress) as any as AlgebraBasePluginV1; - const feeConfig = await pluginMock.feeConfig(); - expect(feeConfig.baseFee).to.be.not.eq(0); - }); - }); - - describe('#CreatePluginForExistingPool', () => { - it('only if has role', async () => { - expect(pluginFactory.connect(other).createPluginForExistingPool(wallet.address, other.address)).to.be.revertedWithoutReason; - }); - - it('cannot create for nonexistent pool', async () => { - await expect(pluginFactory.createPluginForExistingPool(wallet.address, other.address)).to.be.revertedWith('Pool not exist'); - }); - - it('can create for existing pool', async () => { - await mockAlgebraFactory.stubPool(wallet.address, other.address, other.address); - - await pluginFactory.createPluginForExistingPool(wallet.address, other.address); - const pluginAddress = await pluginFactory.pluginByPool(other.address); - expect(pluginAddress).to.not.be.eq(ZERO_ADDRESS); - const pluginMock = (await ethers.getContractFactory('AlgebraBasePluginV1')).attach(pluginAddress) as any as AlgebraBasePluginV1; - const feeConfig = await pluginMock.feeConfig(); - expect(feeConfig.baseFee).to.be.not.eq(0); - }); - - it('cannot create twice for existing pool', async () => { - await mockAlgebraFactory.stubPool(wallet.address, other.address, other.address); - - await pluginFactory.createPluginForExistingPool(wallet.address, other.address); - - await expect(pluginFactory.createPluginForExistingPool(wallet.address, other.address)).to.be.revertedWith('Already created'); - }); - }); - - describe('#Default fee configuration', () => { - describe('#setDefaultFeeConfiguration', () => { - const configuration = { - alpha1: 3002, - alpha2: 10009, - beta1: 1001, - beta2: 1006, - gamma1: 20, - gamma2: 22, - baseFee: 150, - }; - it('fails if caller is not owner', async () => { - await expect(pluginFactory.connect(other).setDefaultFeeConfiguration(configuration)).to.be.revertedWith('Only administrator'); - }); - - it('updates defaultFeeConfiguration', async () => { - await pluginFactory.setDefaultFeeConfiguration(configuration); - - const newConfig = await pluginFactory.defaultFeeConfiguration(); - - expect(newConfig.alpha1).to.eq(configuration.alpha1); - expect(newConfig.alpha2).to.eq(configuration.alpha2); - expect(newConfig.beta1).to.eq(configuration.beta1); - expect(newConfig.beta2).to.eq(configuration.beta2); - expect(newConfig.gamma1).to.eq(configuration.gamma1); - expect(newConfig.gamma2).to.eq(configuration.gamma2); - expect(newConfig.baseFee).to.eq(configuration.baseFee); - }); - - it('emits event', async () => { - await expect(pluginFactory.setDefaultFeeConfiguration(configuration)) - .to.emit(pluginFactory, 'DefaultFeeConfiguration') - .withArgs([ - configuration.alpha1, - configuration.alpha2, - configuration.beta1, - configuration.beta2, - configuration.gamma1, - configuration.gamma2, - configuration.baseFee, - ]); - }); - - it('cannot exceed max fee', async () => { - const conf2 = { ...configuration }; - conf2.alpha1 = 30000; - conf2.alpha2 = 30000; - conf2.baseFee = 15000; - await expect(pluginFactory.setDefaultFeeConfiguration(conf2)).to.be.revertedWith('Max fee exceeded'); - }); - - it('cannot set zero gamma', async () => { - let conf2 = { ...configuration }; - conf2.gamma1 = 0; - await expect(pluginFactory.setDefaultFeeConfiguration(conf2)).to.be.revertedWith('Gammas must be > 0'); - - conf2 = { ...configuration }; - conf2.gamma2 = 0; - await expect(pluginFactory.setDefaultFeeConfiguration(conf2)).to.be.revertedWith('Gammas must be > 0'); - - conf2 = { ...configuration }; - conf2.gamma1 = 0; - conf2.gamma2 = 0; - await expect(pluginFactory.setDefaultFeeConfiguration(conf2)).to.be.revertedWith('Gammas must be > 0'); - }); - }); - }); - - describe('#setFarmingAddress', () => { - it('fails if caller is not owner', async () => { - await expect(pluginFactory.connect(other).setFarmingAddress(wallet.address)).to.be.revertedWith('Only administrator'); - }); - - it('updates farmingAddress', async () => { - await pluginFactory.setFarmingAddress(other.address); - expect(await pluginFactory.farmingAddress()).to.eq(other.address); - }); - - it('emits event', async () => { - await expect(pluginFactory.setFarmingAddress(other.address)).to.emit(pluginFactory, 'FarmingAddress').withArgs(other.address); - }); - - it('cannot set current address', async () => { - await pluginFactory.setFarmingAddress(other.address); - await expect(pluginFactory.setFarmingAddress(other.address)).to.be.reverted; - }); - }); -}); diff --git a/src/plugin/test/BasePluginV2Factory.spec.ts b/src/plugin/test/BasePluginV2Factory.spec.ts deleted file mode 100644 index 787c6f5df..000000000 --- a/src/plugin/test/BasePluginV2Factory.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { expect } from './shared/expect'; -import { ZERO_ADDRESS, pluginFactoryFixtureV2 } from './shared/fixtures'; - -import { BasePluginV2Factory, AlgebraBasePluginV2, MockFactory } from '../typechain'; - -describe('BasePluginV2Factory', () => { - let wallet: Wallet, other: Wallet; - - let pluginFactory: BasePluginV2Factory; - let mockAlgebraFactory: MockFactory; - - before('prepare signers', async () => { - [wallet, other] = await (ethers as any).getSigners(); - }); - - beforeEach('deploy test volatilityOracle', async () => { - ({ pluginFactory, mockFactory: mockAlgebraFactory } = await loadFixture(pluginFactoryFixtureV2)); - }); - - describe('#Create plugin', () => { - it('only factory', async () => { - expect(pluginFactory.beforeCreatePoolHook(wallet.address, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x')).to.be - .revertedWithoutReason; - }); - - it('factory can create plugin', async () => { - const pluginFactoryFactory = await ethers.getContractFactory('BasePluginV2Factory'); - const pluginFactoryMock = (await pluginFactoryFactory.deploy(wallet.address)) as any as BasePluginV2Factory; - - const pluginAddress = await pluginFactoryMock.beforeCreatePoolHook.staticCall( - wallet.address, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS, - '0x' - ); - await pluginFactoryMock.beforeCreatePoolHook(wallet.address, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x'); - - const pluginMock = (await ethers.getContractFactory('AlgebraBasePluginV2')).attach(pluginAddress) as any as AlgebraBasePluginV2; - const baseFee = await pluginMock.s_baseFee(); - expect(baseFee).to.be.not.eq(0); - }); - }); - - describe('#CreatePluginForExistingPool', () => { - it('only if has role', async () => { - expect(pluginFactory.connect(other).createPluginForExistingPool(wallet.address, other.address)).to.be.revertedWithoutReason; - }); - - it('cannot create for nonexistent pool', async () => { - await expect(pluginFactory.createPluginForExistingPool(wallet.address, other.address)).to.be.revertedWith('Pool not exist'); - }); - - it('can create for existing pool', async () => { - await mockAlgebraFactory.stubPool(wallet.address, other.address, other.address); - - await pluginFactory.createPluginForExistingPool(wallet.address, other.address); - const pluginAddress = await pluginFactory.pluginByPool(other.address); - expect(pluginAddress).to.not.be.eq(ZERO_ADDRESS); - const pluginMock = (await ethers.getContractFactory('AlgebraBasePluginV2')).attach(pluginAddress) as any as AlgebraBasePluginV2; - const baseFee = await pluginMock.s_baseFee(); - expect(baseFee).to.be.not.eq(0); - }); - - it('cannot create twice for existing pool', async () => { - await mockAlgebraFactory.stubPool(wallet.address, other.address, other.address); - - await pluginFactory.createPluginForExistingPool(wallet.address, other.address); - - await expect(pluginFactory.createPluginForExistingPool(wallet.address, other.address)).to.be.revertedWith('Already created'); - }); - }); - - describe('#Default base fee ', () => { - describe('#setDefaultBaseFee', () => { - - it('fails if caller is not owner', async () => { - await expect(pluginFactory.connect(other).setDefaultBaseFee(1000)).to.be.revertedWith('Only administrator'); - }); - - it('fails if try to set same value', async () => { - await expect(pluginFactory.connect(other).setDefaultBaseFee(500)).to.be.reverted; - }); - - it('updates defaultFeeConfiguration', async () => { - await pluginFactory.setDefaultBaseFee(1000); - - const newFee = await pluginFactory.defaultBaseFee(); - - expect(newFee).to.eq(1000); - }); - - it('emits event', async () => { - await expect(pluginFactory.setDefaultBaseFee(1000)) - .to.emit(pluginFactory, 'DefaultBaseFee') - .withArgs(1000); - }); - - }); - }); - - describe('#setFarmingAddress', () => { - it('fails if caller is not owner', async () => { - await expect(pluginFactory.connect(other).setFarmingAddress(wallet.address)).to.be.revertedWith('Only administrator'); - }); - - it('updates farmingAddress', async () => { - await pluginFactory.setFarmingAddress(other.address); - expect(await pluginFactory.farmingAddress()).to.eq(other.address); - }); - - it('emits event', async () => { - await expect(pluginFactory.setFarmingAddress(other.address)).to.emit(pluginFactory, 'FarmingAddress').withArgs(other.address); - }); - - it('cannot set current address', async () => { - await pluginFactory.setFarmingAddress(other.address); - await expect(pluginFactory.setFarmingAddress(other.address)).to.be.reverted; - }); - }); -}); diff --git a/src/plugin/test/OracleLibrary.spec.ts b/src/plugin/test/OracleLibrary.spec.ts deleted file mode 100644 index 3ad581fe1..000000000 --- a/src/plugin/test/OracleLibrary.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { ContractFactory } from 'ethers'; -import { OracleLibraryTest, TestERC20 } from '../typechain'; -import { expandTo18Decimals } from './shared/utilities'; -import snapshotGasCost from './shared/snapshotGasCost'; -import { tokensFixture } from './shared/externalFixtures'; - -describe('OracleLibrary', () => { - let tokens: TestERC20[]; - let oracleLibraryTest: OracleLibraryTest; - - const oracleLibraryTestFixture = async () => { - const tokensFixtureRes = await tokensFixture(); - tokens = [tokensFixtureRes.token0, tokensFixtureRes.token1]; - - const oracleLibraryFactory = await ethers.getContractFactory('OracleLibraryTest'); - const oracleLibrary = await oracleLibraryFactory.deploy(); - - return { - tokens: tokens as TestERC20[], - oracleLibraryTest: oracleLibrary as any as OracleLibraryTest, - }; - }; - - beforeEach('deploy fixture', async () => { - const fixtures = await loadFixture(oracleLibraryTestFixture); - tokens = fixtures.tokens; - oracleLibraryTest = fixtures.oracleLibraryTest; - }); - - describe('#consult', () => { - let mockVolatilityOracleFactory: ContractFactory; - - before('create mockVolatilityOracleFactory', async () => { - mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - }); - - it('reverts when period is 0', async () => { - await expect(oracleLibraryTest.consult(oracleLibraryTest, 0)).to.be.revertedWith('Period is zero'); - }); - - it('correct output when tick is 0', async () => { - const period = 3; - const tickCumulatives = [12n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - const oracleLibraryTick = await oracleLibraryTest.consult(mockVolatilityOracle, period); - - expect(oracleLibraryTick).to.equal(0n); - }); - - it('correct output for positive tick', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - const oracleLibraryTick = await oracleLibraryTest.consult(mockVolatilityOracle, period); - - // Always round to negative infinity - // In this case, we don't have do anything - expect(oracleLibraryTick).to.equal(1n); - }); - - it('correct output for negative tick', async () => { - const period = 3; - const tickCumulatives = [-7n, -12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - const oracleLibraryTick = await oracleLibraryTest.consult(mockVolatilityOracle, period); - - // Always round to negative infinity - // In this case, we need to subtract one because integer division rounds to 0 - expect(oracleLibraryTick).to.equal(-2n); - }); - - it('correct rounding for .5 negative tick', async () => { - const period = 4; - const tickCumulatives = [-10n, -12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - const oracleLibraryTick = await oracleLibraryTest.consult(mockVolatilityOracle, period); - - // Always round to negative infinity - // In this case, we need to subtract one because integer division rounds to 0 - expect(oracleLibraryTick).to.equal(-1n); - }); - - it('gas test [ @skip-on-coverage ]', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 0], tickCumulatives); - - await snapshotGasCost(oracleLibraryTest.getGasCostOfConsult(mockVolatilityOracle, period)); - }); - }); - - describe('#oldestTimepointMetadata', () => { - it('returns correct value without overflow', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 1], tickCumulatives); - - const oldestTimepointMetadata = await oracleLibraryTest.oldestTimepointMetadata(mockVolatilityOracle); - expect(oldestTimepointMetadata.index).to.be.eq(0); - expect(oldestTimepointMetadata.timestamp).to.be.eq(period); - }); - - it('returns correct value with overflow', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 2], tickCumulatives); - await mockVolatilityOracle.setTimepoint(2, true, 1000, 10, 20); - - const oldestTimepointMetadata = await oracleLibraryTest.oldestTimepointMetadata(mockVolatilityOracle); - expect(oldestTimepointMetadata.index).to.be.eq(2); - expect(oldestTimepointMetadata.timestamp).to.be.eq(1000); - }); - }); - - describe('#latestTimepointMetadata', () => { - it('returns correct value', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 1], tickCumulatives); - - const oldestTimepointMetadata = await oracleLibraryTest.lastTimepointMetadata(mockVolatilityOracle); - expect(oldestTimepointMetadata.index).to.be.eq(1); - expect(oldestTimepointMetadata.timestamp).to.be.eq(101); - }); - }); - - describe('#isInitialized', () => { - it('returns correct value', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 1], tickCumulatives); - - const result = await oracleLibraryTest.isInitialized(mockVolatilityOracle); - expect(result).to.be.eq(true); - }); - }); - - describe('#isConnected', () => { - it('returns correct value', async () => { - const period = 3; - const tickCumulatives = [7n, 12n]; - const mockVolatilityOracleFactory = await ethers.getContractFactory('MockVolatilityOracle'); - const mockVolatilityOracle = await mockVolatilityOracleFactory.deploy([period, 1], tickCumulatives); - - const mockPoolFactory = await ethers.getContractFactory('MockPool'); - const mockPool = await mockPoolFactory.deploy(); - - expect(await oracleLibraryTest.isConnected(mockVolatilityOracle, mockPool)).to.be.false; - - await mockPool.setPlugin(mockVolatilityOracle); - expect(await oracleLibraryTest.isConnected(mockVolatilityOracle, mockPool)).to.be.false; - - await mockPool.setPluginConfig(1); - expect(await oracleLibraryTest.isConnected(mockVolatilityOracle, mockPool)).to.be.true; - }); - }); - - describe('#getQuoteAtTick', () => { - // sanity check - it('token0: returns correct value when tick = 0', async () => { - const quoteAmount = await oracleLibraryTest.getQuoteAtTick(0n, expandTo18Decimals(1), tokens[0], tokens[1]); - - expect(quoteAmount).to.equal(expandTo18Decimals(1)); - }); - - // sanity check - it('token1: returns correct value when tick = 0', async () => { - const quoteAmount = await oracleLibraryTest.getQuoteAtTick(0n, expandTo18Decimals(1), tokens[1], tokens[0]); - - expect(quoteAmount).to.equal(expandTo18Decimals(1)); - }); - - it('token0: returns correct value when at min tick | 0 < sqrtRatioX96 <= type(uint128).max', async () => { - const quoteAmount = await oracleLibraryTest.getQuoteAtTick(-887272n, 2n ** 128n - 1n, tokens[0], tokens[1]); - expect(quoteAmount).to.equal(1n); - }); - - it('token1: returns correct value when at min tick | 0 < sqrtRatioX96 <= type(uint128).max', async () => { - const quoteAmount = await oracleLibraryTest.getQuoteAtTick(-887272n, 2n ** 128n - 1n, tokens[1], tokens[0]); - expect(quoteAmount).to.equal('115783384738768196242144082653949453838306988932806144552194799290216044976282'); - }); - - it('token0: returns correct value when at max tick | sqrtRatioX96 > type(uint128).max', async () => { - const quoteAmount = await oracleLibraryTest.getQuoteAtTick(887272n, 2n ** 128n - 1n, tokens[0], tokens[1]); - expect(quoteAmount).to.equal('115783384785599357996676985412062652720342362943929506828539444553934033845703'); - }); - - it('token1: returns correct value when at max tick | sqrtRatioX96 > type(uint128).max', async () => { - const quoteAmount = await oracleLibraryTest.getQuoteAtTick(887272n, 2n ** 128n - 1n, tokens[1], tokens[0]); - expect(quoteAmount).to.equal(1n); - }); - - it('gas test [ @skip-on-coverage ]', async () => { - await snapshotGasCost(oracleLibraryTest.getGasCostOfGetQuoteAtTick(10n, expandTo18Decimals(1), tokens[0], tokens[1])); - }); - }); -}); diff --git a/src/plugin/test/SlidingFee.spec.ts b/src/plugin/test/SlidingFee.spec.ts deleted file mode 100644 index 06239ca89..000000000 --- a/src/plugin/test/SlidingFee.spec.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { expect } from './shared/expect'; -import { ethers } from 'hardhat'; -import { SlidingFeeTest } from '../typechain'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import snapshotGasCost from './shared/snapshotGasCost'; - -describe('SlidingFee', () => { - let slidingFeePlugin: SlidingFeeTest; - - async function slidingFeeFixture() { - const factory = await ethers.getContractFactory('SlidingFeeTest'); - return (await factory.deploy(100)) as any as SlidingFeeTest; - } - - beforeEach('deploy SlidingFeeTest', async () => { - slidingFeePlugin = await loadFixture(slidingFeeFixture); - }); - - it('set config', async () => { - await slidingFeePlugin.changeBaseFee(500) - await slidingFeePlugin.changeFactor(1000) - - expect(await slidingFeePlugin.s_baseFee()).to.be.eq(500) - expect(await slidingFeePlugin.s_priceChangeFactor()).to.be.eq(1000) - }); - - describe('#FeeFactors', () => { - beforeEach('set config', async () => { - await slidingFeePlugin.changeBaseFee(500) - await slidingFeePlugin.changeFactor(1000) - }); - - for (const factor of [500, 1000, 2000]) { - it("Shifts correct with positive price change, factor is " + factor, async function () { - - await slidingFeePlugin.changeFactor(factor) - // swap, price increased x2 (otz) - let lastTick = 10000 - let currentTick = 16932 - - await slidingFeePlugin.getFeeForSwap(false, lastTick, currentTick); - - if (factor == 500) { - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately((3n << 96n) / 2n, 1n << 81n); // 1.5 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately(1n << 95n, 1n << 81n); // 0.5 - } - - if (factor == 1000) { - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately(2n << 96n, 1n << 81n); // 2 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately(0n << 96n, 1n << 81n); // 0 - } - - if (factor == 2000) { - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.eq(2n << 96n); // 2 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.eq(0n << 96n); // 0 - } - }); - - it("Shifts correct with negative price change, factor is " + factor, async function () { - await slidingFeePlugin.changeFactor(factor) - - // swap, price decreased x0.25 (zto) - let lastTick = 16932 - let currentTick = 10000 - - await slidingFeePlugin.getFeeForSwap(false, lastTick, currentTick); - - if (factor == 500) { - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately((3n << 96n )/ 4n, 1n << 81n); // 0.75 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately((5n << 96n) / 4n, 1n << 81n); // 1.25 - } - - if (factor == 1000) { - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately(1n << 95n, 1n << 81n); // 0 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately((3n << 96n) / 2n, 1n << 81n); // 2 - } - - if (factor == 2000) { - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.eq(0n << 96n); // 0 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.eq(2n << 96n); // 2 - } - }); - } - - - - it("Factors should be reset", async function () { - - // swap, price increased x1.5 (otz) - let lastTick = 10000 - let currentTick = 14055 - await slidingFeePlugin.getFeeForSwap(false, lastTick, currentTick); // 1.5, 0.5 - - // swap, price decreased x0.5 (zto) - lastTick = 14055 - currentTick = 7123 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); // 1, 1 - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately(1n << 96n, 1n << 81n); // 1 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately(1n << 96n, 1n << 81n); // 1 - }); - - it("Huge swap otz", async function () { - - // swap, price changed from min to max - let lastTick = -887272 - let currentTick = 887272 - - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.eq(2n << 96n); // 2 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.eq(0n << 96n); // 0 - }); - - it("Huge swap zto", async function () { - - // swap, price changed from min to max - let lastTick = 887272 - let currentTick = -887272 - - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.eq(0n << 96n); // 0 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.eq(2n << 96n); // 2 - }); - - it("Shift correct after two oneToZero movements", async function () { - await slidingFeePlugin.changeFactor(500) - // swap, price increased x2 (otz) - let lastTick = 10000 - let currentTick = 16932 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - // swap, price increased x1.5 (otz) - lastTick = 16932 - currentTick = 20987 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately((7n << 96n) / 4n, 1n << 81n); // 1.75 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately((1n << 96n) / 4n, 1n << 81n); // 0.25 - }); - - it("Shift correct after two zeroToOne movements", async function () { - await slidingFeePlugin.changeFactor(500) - // swap, price decreased x0.5 (zt0) - let lastTick = 20987 - let currentTick = 14055 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - - // swap, price decreased x0.5 (zt0) - lastTick = 14055 - currentTick = 7123 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately(1n << 95n , 1n << 81n); // 0.5 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately((3n << 96n) / 2n, 1n << 81n); // 1.5 - }); - - it("Shift correct after two oneToZero movements(negative ticks)", async function () { - await slidingFeePlugin.changeFactor(500) - // swap, price increased x2 (otz) - let lastTick = -20987 - let currentTick = -14055 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - - // swap, price increased x1.5(otz) - lastTick = -14055 - currentTick = -10000 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately((7n << 96n) / 4n, 1n << 81n); // 1.75 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately((1n << 96n) / 4n, 1n << 81n); // 0.25 - - }); - - it("Shift correct after two zeroToOne movements(negative ticks)", async function () { - await slidingFeePlugin.changeFactor(500) - // swap, price decreased x0.5 (zto) - let lastTick = -10000 - let currentTick = -16932 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - // swap, price decreased x0.5 (zto) - lastTick = -16932 - currentTick = -23864 - await slidingFeePlugin.getFeeForSwap(true, lastTick, currentTick); - - expect((await slidingFeePlugin.s_feeFactors()).oneToZeroFeeFactor).to.be.approximately(1n << 95n, 1n << 81n); // 0.5 - expect((await slidingFeePlugin.s_feeFactors()).zeroToOneFeeFactor).to.be.approximately((3n << 96n) / 2n, 1n << 81n); // 1.5 - }); - - }); - - describe('#getSlidingFee', () => { - - async function getFee(zto: boolean, lastTick: number, currentTick: number) : Promise{ - let tx = await slidingFeePlugin.getFeeForSwap(zto, lastTick, currentTick); - return (await tx.wait()).logs[0].args['fee'] - } - - beforeEach('set config', async () => { - await slidingFeePlugin.changeBaseFee(500) - await slidingFeePlugin.changeFactor(1000) - }); - - it("returns base fee value", async function () { - let fee = await getFee(false, 10000, 10000) - expect(fee).to.be.eq(500) - }); - - it("one to zero fee should be increased x1.5", async function () { - let feeOtZ = await getFee(false, 10000, 14055) - expect(feeOtZ).to.be.eq(750) - }); - - it("zero to one fee should be decreased x1.5", async function () { - let feeZtO = await getFee(true, 10000, 14054) - expect(feeZtO).to.be.eq(250) - }); - - it("handle overflow", async function () { - await slidingFeePlugin.changeBaseFee(50000) - let feeOtZ = await getFee(false, 10000,100000) - expect(feeOtZ).to.be.eq(65535) - }); - - it("MIN fee is 1 (0.0001%)", async function () { - await slidingFeePlugin.changeBaseFee(50000) - let feeOtZ = await getFee(true, 10000,100000) - expect(feeOtZ).to.be.eq(1) - }); - - }) - - - describe('#getFee gas cost [ @skip-on-coverage ]', () => { - it('gas cost of same tick', async () => { - await snapshotGasCost(slidingFeePlugin.getGasCostOfGetFeeForSwap(true, 100, 100)); - }); - - it('gas cost of tick increase', async () => { - await snapshotGasCost(slidingFeePlugin.getGasCostOfGetFeeForSwap(true, 10000, 40000)); - }); - - it('gas cost of tick decrease', async () => { - await snapshotGasCost(slidingFeePlugin.getGasCostOfGetFeeForSwap(false, 40000, 10000)); - }); - }); - -}); \ No newline at end of file diff --git a/src/plugin/test/VolatilityOracle.spec.ts b/src/plugin/test/VolatilityOracle.spec.ts deleted file mode 100644 index 84601a5f4..000000000 --- a/src/plugin/test/VolatilityOracle.spec.ts +++ /dev/null @@ -1,1054 +0,0 @@ -import { BigNumberish, Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { VolatilityOracleTest } from '../typechain'; -import checkTimepointEquals from './shared/checkTimepointEquals'; -import { expect } from './shared/expect'; -import { TEST_POOL_START_TIME } from './shared/fixtures'; -import snapshotGasCost from './shared/snapshotGasCost'; - -describe('VolatilityOracle', () => { - let wallet: Wallet; - - before('create fixture loader', async () => { - [wallet] = await (ethers as any).getSigners(); - }); - - const volatilityOracleFixture = async () => { - const volatilityOracleTestFactory = await ethers.getContractFactory('VolatilityOracleTest'); - return (await volatilityOracleTestFactory.deploy()) as any as VolatilityOracleTest; - }; - - const initializedVolatilityOracleFixture = async () => { - const volatilityOracle = await volatilityOracleFixture(); - await volatilityOracle.initialize({ - time: 0, - tick: 0, - }); - return volatilityOracle; - }; - - describe('#initialize', () => { - let volatilityOracle: VolatilityOracleTest; - beforeEach('deploy test volatilityOracle', async () => { - volatilityOracle = await loadFixture(volatilityOracleFixture); - }); - it('cannot initialize twice', async () => { - await volatilityOracle.initialize({ tick: 1, time: 1 }); - await expect(volatilityOracle.initialize({ tick: 1, time: 1 })).to.be.revertedWithCustomError( - volatilityOracle, - 'volatilityOracleAlreadyInitialized' - ); - }); - it('index is 0', async () => { - await volatilityOracle.initialize({ tick: 1, time: 1 }); - expect(await volatilityOracle.index()).to.eq(0); - }); - it('sets first slot timestamp only', async () => { - await volatilityOracle.initialize({ tick: 1, time: 1 }); - checkTimepointEquals(await volatilityOracle.timepoints(0), { - initialized: true, - blockTimestamp: 1n, - tickCumulative: 0n, - }); - }); - it('gas [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.initialize({ tick: 1, time: 1 })); - }); - it('should return interpolated volatility with gap > window', async () => { - await volatilityOracle.initialize({ tick: 46054, time: 1 }); - await volatilityOracle.update({ advanceTimeBy: 100, tick: 80054 }); - await volatilityOracle.update({ advanceTimeBy: 1, tick: 46054 }); - await volatilityOracle.getTimepoints([0]); - await volatilityOracle.getTimepoints([1]); - }); - }); - - describe('#write', () => { - let volatilityOracle: VolatilityOracleTest; - - beforeEach('deploy initialized test volatilityOracle', async () => { - volatilityOracle = await loadFixture(initializedVolatilityOracleFixture); - }); - - it('does nothing if time has not changed', async () => { - await volatilityOracle.update({ advanceTimeBy: 1, tick: 3 }); - expect(await volatilityOracle.index()).to.eq(1); - await volatilityOracle.update({ advanceTimeBy: 0, tick: -5 }); - expect(await volatilityOracle.index()).to.eq(1); - }); - - it('writes an index if time has changed', async () => { - await volatilityOracle.update({ advanceTimeBy: 6, tick: 3 }); - expect(await volatilityOracle.index()).to.eq(1); - await volatilityOracle.update({ advanceTimeBy: 4, tick: -5 }); - - expect(await volatilityOracle.index()).to.eq(2); - checkTimepointEquals(await volatilityOracle.timepoints(1), { - tickCumulative: 0n, - initialized: true, - blockTimestamp: 6n, - }); - }); - - it('accumulates liquidity', async () => { - await volatilityOracle.update({ advanceTimeBy: 3, tick: 3 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: -7 }); - await volatilityOracle.update({ advanceTimeBy: 5, tick: -2 }); - - expect(await volatilityOracle.index()).to.eq(3); - - checkTimepointEquals(await volatilityOracle.timepoints(1), { - initialized: true, - tickCumulative: 0n, - blockTimestamp: 3n, - }); - checkTimepointEquals(await volatilityOracle.timepoints(2), { - initialized: true, - tickCumulative: 12n, - blockTimestamp: 7n, - }); - checkTimepointEquals(await volatilityOracle.timepoints(3), { - initialized: true, - tickCumulative: -23n, - blockTimestamp: 12n, - }); - checkTimepointEquals(await volatilityOracle.timepoints(4), { - initialized: false, - tickCumulative: 0n, - blockTimestamp: 0n, - }); - }); - }); - - describe('#getAverageTick', () => { - let window = 24 * 60 * 60; - let volatilityOracle: VolatilityOracleTest; - beforeEach('deploy initialized test volatilityOracle', async () => { - volatilityOracle = await loadFixture(volatilityOracleFixture); - }); - - it('in the same block with init', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7200); - }); - - it('does not change after write', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 60 * 60, tick: 7500 }); - await volatilityOracle.advanceTime(60 * 5); - const tickBeforeWrite = await volatilityOracle.getAverageTick(); - await volatilityOracle.update({ advanceTimeBy: 0, tick: 100000 }); - const tickAfterWrite = await volatilityOracle.getAverageTick(); - expect(tickBeforeWrite).to.be.eq(tickAfterWrite); - await volatilityOracle.advanceTime(1); - expect(tickBeforeWrite).to.be.not.eq(await volatilityOracle.getAverageTick()); - }); - - it('in exactly 24h ago', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: window, tick: 7400 }); - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7300); - }); - - it('last index is exactly 24h ago', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.advanceTime(window); - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7300); - }); - - it('last index is more then 24h ago', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.advanceTime(window + 1); - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7300); - - const tickCumulative = await volatilityOracle.getTickCumulativeAt(0); - expect(tickCumulative).to.be.eq(630741700); - }); - - it('find tick cumulative at the start of previous window', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: window, tick: 7350 }); - await volatilityOracle.advanceTime(1); - - const tickCumulative = await volatilityOracle.getTickCumulativeAt(window + 1); - expect(tickCumulative).to.be.eq(14400); - expect(await volatilityOracle.getTickCumulativeAt(window)).to.be.eq(tickCumulative + 7300n); - expect(await volatilityOracle.getTickCumulativeAt(window - 1)).to.be.eq(tickCumulative + 7300n * 2n); - }); - - describe('oldest timepoint is more than WINDOW seconds ago', async () => { - beforeEach('initialize', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: window / 2, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: window / 2 + 1, tick: 7350 }); - }); - - it('last timepoint is target', async () => { - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7250); - }); - - it('target is after last timepoint', async () => { - await volatilityOracle.advanceTime(10); - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7250); - }); - }); - - describe('oldest timepoint is less than WINDOW seconds ago', async () => { - beforeEach('initialize', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7250 }); - await volatilityOracle.update({ advanceTimeBy: window / 2, tick: 7230 }); - }); - - it('last timepoint is target', async () => { - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7249); - }); - - it('target is after last timepoint', async () => { - await volatilityOracle.advanceTime(10); - const tick = await volatilityOracle.getAverageTick(); - expect(tick).to.be.eq(7249); - }); - }); - - it('potential overflow scenario', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - let averageTick = await volatilityOracle.getAverageTick(); - expect(averageTick).to.be.lt(7300); - expect(averageTick).to.be.gt(7100); - await volatilityOracle.update({ advanceTimeBy: 60 * 60, tick: 7300 }); - averageTick = await volatilityOracle.getAverageTick(); - expect(averageTick).to.be.lt(7300); - expect(averageTick).to.be.gt(7199); - - await volatilityOracle.update({ advanceTimeBy: window - 2, tick: 7400 }); - averageTick = await volatilityOracle.getAverageTick(); - expect(averageTick).to.be.lt(7400); - expect(averageTick).to.be.gt(7200); - - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7600 }); - averageTick = await volatilityOracle.getAverageTick(); - expect(averageTick).to.be.lt(7400); - expect(averageTick).to.be.gt(7200); - }); - }); - - describe('#getAverageVolatility', () => { - let volatilityOracle: VolatilityOracleTest; - const window = 24 * 60 * 60; - beforeEach('deploy initialized test volatilityOracle', async () => { - volatilityOracle = await loadFixture(volatilityOracleFixture); - }); - - it('in the same block with init', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(0); - }); - - it('in exactly 24h ago', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: window, tick: 7500 }); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(3333); - }); - - it('last index is exactly 24h ago', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.advanceTime(window); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(3333); - }); - - it('last index is more then 24h ago', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.advanceTime(window + 1); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(3333); - }); - - it('specific case for binary search (atOrAfter)', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: 2 * 60 * 60, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: 2 * 60 * 60, tick: 73100 }); - await volatilityOracle.advanceTime(window - 2 * 60 * 60); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(1442410782); - }); - - describe('oldest timepoint is more than WINDOW seconds ago', async () => { - beforeEach('initialize', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: window / 2, tick: 7300 }); - await volatilityOracle.update({ advanceTimeBy: window / 2 + 1, tick: 7350 }); - }); - - it('last timepoint is target', async () => { - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(2916); - }); - - it('target is after last timepoint', async () => { - await volatilityOracle.advanceTime(10); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(2917); - }); - }); - - describe('oldest timepoint is less than WINDOW seconds ago', async () => { - beforeEach('initialize', async () => { - await volatilityOracle.initialize({ tick: 7200, time: 1000 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: 7250 }); - await volatilityOracle.update({ advanceTimeBy: window / 2, tick: 7230 }); - }); - - it('last timepoint is target', async () => { - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(850); - }); - - it('target is after last timepoint', async () => { - await volatilityOracle.advanceTime(10); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(850); - }); - }); - }); - - describe('#getTimepoints', () => { - describe('before initialization', async () => { - let volatilityOracle: VolatilityOracleTest; - beforeEach('deploy test volatilityOracle', async () => { - volatilityOracle = await loadFixture(volatilityOracleFixture); - }); - - const getSingleTimepoint = async (secondsAgo: number) => { - const { - tickCumulatives: [tickCumulative], - volatilityCumulatives: [volatilityCumulative], - } = await volatilityOracle.getTimepoints([secondsAgo]); - return { tickCumulative, volatilityCumulative }; - }; - - it('fails if an older timepoint does not exist', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await expect(getSingleTimepoint(1)).to.be.revertedWithCustomError(volatilityOracle, 'targetIsTooOld'); - }); - - it('does not fail across overflow boundary', async () => { - await volatilityOracle.initialize({ tick: 2, time: 2 ** 32 - 1 }); - await volatilityOracle.advanceTime(2); - const { tickCumulative } = await getSingleTimepoint(1); - expect(tickCumulative).to.be.eq(2); - }); - - it('single timepoint at current time', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(0); - }); - - it('timepoint does not change after time', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 24 * 60, tick: 1500 }); - await volatilityOracle.advanceTime(10 * 60); - const { tickCumulative: tickCumulativeBefore } = await getSingleTimepoint(0); - await volatilityOracle.advanceTime(5); - const { tickCumulative: tickCumulativeAfter } = await getSingleTimepoint(5); - expect(tickCumulativeBefore).to.be.eq(tickCumulativeAfter); - const { tickCumulative: tickCumulativeNew } = await getSingleTimepoint(0); - expect(tickCumulativeBefore).to.be.not.eq(tickCumulativeNew); - }); - - it('single timepoint at current time equal after write', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 60 * 10, tick: 10 }); - await volatilityOracle.update({ advanceTimeBy: 24 * 60, tick: 15 }); - await volatilityOracle.advanceTime(10 * 60); - const { tickCumulative, volatilityCumulative } = await getSingleTimepoint(0); - await volatilityOracle.update({ advanceTimeBy: 0, tick: 15 }); - const { tickCumulative: tickCumulativeAfterWrite, volatilityCumulative: volatilityCumulativeAfterWrite } = await getSingleTimepoint(0); - - expect(tickCumulativeAfterWrite).to.be.eq(tickCumulative); - expect(volatilityCumulativeAfterWrite).to.be.eq(volatilityCumulative); - }); - - it('single timepoint at current time not equal after write and time passed', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 60 * 10, tick: 10 }); - await volatilityOracle.update({ advanceTimeBy: 24 * 60, tick: 15 }); - await volatilityOracle.advanceTime(10 * 60); - const { tickCumulative, volatilityCumulative } = await getSingleTimepoint(0); - await volatilityOracle.update({ advanceTimeBy: 0, tick: 15 }); - await volatilityOracle.advanceTime(10); - const { tickCumulative: tickCumulativeAfterWrite, volatilityCumulative: volatilityCumulativeAfterWrite } = await getSingleTimepoint(0); - - expect(tickCumulativeAfterWrite).to.be.not.eq(tickCumulative); - expect(volatilityCumulativeAfterWrite).to.be.not.eq(volatilityCumulative); - }); - - it('single timepoint in past but not earlier than secondsAgo', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.advanceTime(3); - await expect(getSingleTimepoint(4)).to.be.revertedWithCustomError(volatilityOracle, 'targetIsTooOld'); - }); - - it('single timepoint in past at exactly seconds ago', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.advanceTime(3); - const { tickCumulative } = await getSingleTimepoint(3); - expect(tickCumulative).to.eq(0); - }); - - it('single timepoint in past counterfactual in past', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.advanceTime(3); - const { tickCumulative } = await getSingleTimepoint(1); - expect(tickCumulative).to.eq(4); - }); - - it('single timepoint in past counterfactual now', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.advanceTime(3); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(6); - }); - - it('single timepoint in past exactly at the start of window', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 24 * 60 * 60, tick: 1 }); - const { tickCumulative } = await getSingleTimepoint(24 * 60 * 60); - expect(tickCumulative).to.eq(8); - }); - - it('single timepoint in past exactly at the start of window after some time', async () => { - await volatilityOracle.initialize({ tick: 2, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 24 * 60 * 60, tick: 1 }); - await volatilityOracle.advanceTime(3); - const { tickCumulative } = await getSingleTimepoint(24 * 60 * 60 + 3); - expect(tickCumulative).to.eq(8); - }); - - it('two timepoints in chronological order 0 seconds ago exact', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(-20); - }); - - it('two timepoints in chronological order 0 seconds ago counterfactual', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.advanceTime(7); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(-13); - }); - - it('two timepoints in chronological order seconds ago is exactly on first timepoint', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.advanceTime(7); - const { tickCumulative } = await getSingleTimepoint(11); - expect(tickCumulative).to.eq(0); - }); - - it('two timepoints in chronological order seconds ago is between first and second', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.advanceTime(7); - const { tickCumulative } = await getSingleTimepoint(9); - expect(tickCumulative).to.eq(-10); - }); - - it('two timepoints in reverse order 0 seconds ago exact', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: -5 }); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(-17); - }); - - it('two timepoints in reverse order 0 seconds ago counterfactual', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: -5 }); - await volatilityOracle.advanceTime(7); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(-52); - }); - - it('two timepoints in reverse order seconds ago is exactly on first timepoint', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: -5 }); - await volatilityOracle.advanceTime(7); - const { tickCumulative } = await getSingleTimepoint(10); - expect(tickCumulative).to.eq(-20); - }); - - it('two timepoints in reverse order seconds ago is between first and second', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: -5 }); - await volatilityOracle.advanceTime(7); - const { tickCumulative } = await getSingleTimepoint(9); - expect(tickCumulative).to.eq(-19); - }); - - it('can fetch multiple timepoints', async () => { - await volatilityOracle.initialize({ time: 5, tick: 2 }); - await volatilityOracle.update({ advanceTimeBy: 13, tick: 6 }); - await volatilityOracle.advanceTime(5); - - const { tickCumulatives } = await volatilityOracle.getTimepoints([0, 3, 8, 13, 15, 18]); - expect(tickCumulatives).to.have.lengthOf(6); - expect(tickCumulatives[0]).to.eq(56); - expect(tickCumulatives[1]).to.eq(38); - expect(tickCumulatives[2]).to.eq(20); - expect(tickCumulatives[3]).to.eq(10); - expect(tickCumulatives[4]).to.eq(6); - expect(tickCumulatives[5]).to.eq(0); - }); - - it('gas for getTimepoints since most recent [ @skip-on-coverage ]', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.advanceTime(2); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([1])); - }); - - it('gas for single timepoint at current time [ @skip-on-coverage ]', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - - it('gas for single timepoint at current time after some time [ @skip-on-coverage ]', async () => { - await volatilityOracle.initialize({ tick: -5, time: 5 }); - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - }); - - for (const startingTime of [5, 2 ** 32 - 5]) { - describe(`initialized with 5 timepoints with starting time of ${startingTime}`, () => { - const volatilityOracleFixture5Timepoints = async () => { - const volatilityOracle = await volatilityOracleFixture(); - await volatilityOracle.initialize({ tick: -5, time: startingTime }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 2, tick: -6 }); - await volatilityOracle.update({ advanceTimeBy: 4, tick: -2 }); - await volatilityOracle.update({ advanceTimeBy: 1, tick: -2 }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: 4 }); - await volatilityOracle.update({ advanceTimeBy: 6, tick: 6 }); - return volatilityOracle; - }; - let volatilityOracle: VolatilityOracleTest; - beforeEach('set up timepoints', async () => { - volatilityOracle = await loadFixture(volatilityOracleFixture5Timepoints); - }); - - const getSingleTimepoint = async (secondsAgo: number) => { - const { - tickCumulatives: [tickCumulative], - } = await volatilityOracle.getTimepoints([secondsAgo]); - return { tickCumulative }; - }; - it('latest timepoint same time as latest', async () => { - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(-21); - }); - it('latest timepoint 5 seconds after latest', async () => { - await volatilityOracle.advanceTime(5); - const { tickCumulative } = await getSingleTimepoint(5); - expect(tickCumulative).to.eq(-21); - }); - it('current timepoint 5 seconds after latest', async () => { - await volatilityOracle.advanceTime(5); - const { tickCumulative } = await getSingleTimepoint(0); - expect(tickCumulative).to.eq(9); - }); - it('between latest timepoint and just before latest timepoint at same time as latest', async () => { - const { tickCumulative } = await getSingleTimepoint(3); - expect(tickCumulative).to.eq(-33); - }); - it('between latest timepoint and just before latest timepoint after the latest timepoint', async () => { - await volatilityOracle.advanceTime(5); - const { tickCumulative } = await getSingleTimepoint(8); - expect(tickCumulative).to.eq(-33); - }); - it('older than oldest reverts', async () => { - await expect(getSingleTimepoint(22)).to.be.revertedWithCustomError(volatilityOracle, 'targetIsTooOld'); - await volatilityOracle.advanceTime(5); - await expect(getSingleTimepoint(27)).to.be.revertedWithCustomError(volatilityOracle, 'targetIsTooOld'); - }); - it('oldest timepoint', async () => { - const { tickCumulative } = await getSingleTimepoint(14); - expect(tickCumulative).to.eq(-13); - }); - it('oldest timepoint after some time', async () => { - await volatilityOracle.advanceTime(6); - const { tickCumulative } = await getSingleTimepoint(20); - expect(tickCumulative).to.eq(-13); - }); - - it('fetch many values', async () => { - await volatilityOracle.advanceTime(6); - const { tickCumulatives } = await volatilityOracle.getTimepoints([20, 17, 13, 10, 5, 1, 0]); - expect({ - tickCumulatives: tickCumulatives.map((tc: any) => tc.toString()), - }).to.matchSnapshot(); - }); - - it('gas all of last 20 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(6); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])); - }); - - it('gas latest equal [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - it('gas latest transform [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - it('gas oldest [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([14])); - }); - it('gas between oldest and oldest + 1 [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([13])); - }); - it('gas middle [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([5])); - }); - }); - } - }); - - describe('index overflow tests', async () => { - let volatilityOracle: VolatilityOracleTest; - const startingTime = 100n; - const MAX_UINT16 = 2n ** 16n - 1n; - const DAY = 24n * 60n * 60n; - beforeEach('deploy test volatilityOracle', async () => { - volatilityOracle = await loadFixture(volatilityOracleFixture); - }); - - it('can overflow in first time', async () => { - await volatilityOracle.initialize({ tick: -5, time: startingTime }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - - await volatilityOracle.writeTimepointDirectly(MAX_UINT16 - 1n, { - initialized: true, - blockTimestamp: startingTime + 2n * DAY, - tickCumulative: -5n * 2n * DAY, - volatilityCumulative: 10n * 2n * DAY, - tick: -5, - averageTick: -5, - windowStartIndex: MAX_UINT16 - 2n, - }); - await volatilityOracle.setState(startingTime + 2n * DAY, MAX_UINT16 - 1n); - await volatilityOracle.advanceTime(DAY + 1n); - - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - expect(await volatilityOracle.index()).to.be.eq(MAX_UINT16); - expect(await volatilityOracle.getOldestIndex()).to.be.eq(0); - - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - expect(await volatilityOracle.index()).to.be.eq(0); - expect(await volatilityOracle.getOldestIndex()).to.be.eq(1); - }); - - it('can overflow twice', async () => { - await volatilityOracle.initialize({ tick: -5, time: startingTime }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - - // first - await volatilityOracle.writeTimepointDirectly(MAX_UINT16 - 1n, { - initialized: true, - blockTimestamp: startingTime + 2n * DAY, - tickCumulative: -5n * 2n * DAY, - volatilityCumulative: 10n * 2n * DAY, - tick: -5, - averageTick: -5, - windowStartIndex: MAX_UINT16 - 2n, - }); - await volatilityOracle.setState(startingTime + 2n * DAY, MAX_UINT16 - 1n); - await volatilityOracle.advanceTime(DAY + 1n); - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - - // second - await volatilityOracle.writeTimepointDirectly(MAX_UINT16 - 1n, { - initialized: true, - blockTimestamp: startingTime + 4n * DAY, - tickCumulative: 1n * 4n * DAY, - volatilityCumulative: 10n * 4n * DAY, - tick: 1, - averageTick: 1, - windowStartIndex: MAX_UINT16 - 2n, - }); - await volatilityOracle.setState(startingTime + 4n * DAY, MAX_UINT16 - 1n); - await volatilityOracle.advanceTime(DAY + 1n); - - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - expect(await volatilityOracle.index()).to.be.eq(MAX_UINT16); - expect(await volatilityOracle.getOldestIndex()).to.be.eq(0); - - await volatilityOracle.update({ advanceTimeBy: 3, tick: 1 }); - expect(await volatilityOracle.index()).to.be.eq(0); - expect(await volatilityOracle.getOldestIndex()).to.be.eq(1); - }); - }); - - describe('full volatilityOracle', function () { - this.timeout(10_200_000); - - let volatilityOracle: VolatilityOracleTest; - - let BATCH_SIZE = 1000; - const step = 13; - - const STARTING_TIME = TEST_POOL_START_TIME; - - const maxedOutVolatilityOracleFixture = async () => { - await ethers.provider.send('hardhat_setBalance', [wallet.address, '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000']); - const _volatilityOracle = await volatilityOracleFixture(); - await _volatilityOracle.initialize({ tick: 0, time: STARTING_TIME }); - - let i = 1; - for (i = 1; i < 65536; i += BATCH_SIZE) { - if (i + BATCH_SIZE > 65536) { - BATCH_SIZE = Math.ceil(65536 / 300) * 300 - i; - console.log('batch update starting at', i); - await _volatilityOracle.batchUpdateFixedTimedelta(BATCH_SIZE); - } else { - console.log('batch update starting at', i); - await _volatilityOracle.batchUpdateFast(BATCH_SIZE); - } - } - console.log('Length:', i); - return _volatilityOracle; - }; - - beforeEach('create a full volatilityOracle', async () => { - volatilityOracle = await loadFixture(maxedOutVolatilityOracleFixture); - }); - - it('index wrapped around', async () => { - expect(await volatilityOracle.index()).to.eq(163); - }); - - async function checkGetPoints(secondsAgo: number, expected?: { tickCumulative: BigNumberish }) { - const { tickCumulatives } = await volatilityOracle.getTimepoints([secondsAgo]); - const check = { - tickCumulative: tickCumulatives[0].toString(), - }; - if (typeof expected === 'undefined') { - expect(check).to.matchSnapshot(); - } else { - expect(check).to.deep.eq({ - tickCumulative: expected.tickCumulative.toString(), - }); - } - } - - it('can getTimepoints into the ordered portion with exact seconds ago', async () => { - await checkGetPoints(100 * step, { - tickCumulative: '-27970560813', - }); - }); - - it('can getTimepoints into the ordered portion with inexact seconds ago', async () => { - await checkGetPoints(100 * step + 5, { - tickCumulative: '-27970232823', - }); - }); - - it('can getTimepoints at exactly the latest timepoint', async () => { - await checkGetPoints(0, { - tickCumulative: '-28055903863', - }); - }); - - it('can getTimepoints at exactly the latest timepoint after some time passes', async () => { - await volatilityOracle.advanceTime(5); - await checkGetPoints(5, { - tickCumulative: '-28055903863', - }); - }); - - it('can getTimepoints after the latest timepoint counterfactual', async () => { - await volatilityOracle.advanceTime(5); - await checkGetPoints(3, { - tickCumulative: '-28056035261', - }); - }); - - it('can getTimepoints into the unordered portion of array at exact seconds ago of timepoint', async () => { - await checkGetPoints(200 * step, { - tickCumulative: '-27885347763', - }); - }); - - it('can getTimepoints into the unordered portion of array at seconds ago between timepoints', async () => { - await checkGetPoints(200 * step + 5, { - tickCumulative: '-27885020273', - }); - }); - - it('can getTimepoints the oldest timepoint 13*65534 seconds ago', async () => { - await checkGetPoints(step * 65534, { - tickCumulative: '-175890', - }); - }); - - it('can getTimepoints the oldest timepoint 13*65534 + 5 seconds ago if time has elapsed', async () => { - await volatilityOracle.advanceTime(5); - await checkGetPoints(step * 65534 + 5, { - tickCumulative: '-175890', - }); - }); - - describe('#getAverageVolatility', () => { - const window = 24 * 60 * 60; - - describe('oldest timepoint is more than WINDOW seconds ago', async () => { - beforeEach('initialize', async () => { - await volatilityOracle.update({ advanceTimeBy: window + 1, tick: 7250 }); - }); - - it('last timepoint is target', async () => { - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(3682928); - }); - - it('target is after last timepoint', async () => { - await volatilityOracle.advanceTime(10); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(4298339); - }); - }); - }); - - it('gas cost of getTimepoints(0) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - it(`gas cost of getTimepoints(200 * ${step}) [ @skip-on-coverage ]`, async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([200 * step])); - }); - it(`gas cost of getTimepoints(200 * ${step} + 5) [ @skip-on-coverage ]`, async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([200 * step + 5])); - }); - it('gas cost of getTimepoints(0) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - it('gas cost of getTimepoints(5) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([5])); - }); - it('gas cost of getTimepoints(middle) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([(65534 / 2) * step])); - }); - it('gas cost of getTimepoints(oldest) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([65534 * step])); - }); - it('gas cost of getTimepoints(oldest) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([65534 * step + 5])); - }); - it('gas cost of getTimepoints(24h ago) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([24 * 60 * 60])); - }); - it('gas cost of getTimepoints(24h ago) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([24 * 60 * 60])); - }); - it('gas cost of getTimepoints(24h ago) after 15 minutes [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(15 * 60); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([24 * 60 * 60])); - }); - - it.skip('second index wrap', async () => { - let i = Number(await volatilityOracle.index()); - for (; i < 65536; i += BATCH_SIZE) { - if (i + BATCH_SIZE > 65536) { - BATCH_SIZE = Math.ceil(65536 / 300) * 300 - i; - console.log('batch update starting at', i); - await volatilityOracle.batchUpdateFixedTimedelta(BATCH_SIZE); - } else { - console.log('batch update starting at', i); - await volatilityOracle.batchUpdateFast(BATCH_SIZE); - } - } - expect(await volatilityOracle.index()).to.eq(163); - }); - }); - - describe('full volatilityOracle, maximal density', function () { - this.timeout(10_200_000); - - let volatilityOracle: VolatilityOracleTest; - - let BATCH_SIZE = 1000; - let step = 1; - - const STARTING_TIME = TEST_POOL_START_TIME; - - const maxedOutVolatilityOracleFixture = async () => { - await ethers.provider.send('hardhat_setBalance', [wallet.address, '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000']); - const _volatilityOracle = await volatilityOracleFixture(); - await _volatilityOracle.initialize({ tick: 0, time: STARTING_TIME }); - await _volatilityOracle.setStep(step); - - let i = 1; - for (i = 1; i < 65536; i += BATCH_SIZE) { - if (i + BATCH_SIZE > 65536) { - BATCH_SIZE = Math.ceil(65536 / 300) * 300 - i; - console.log('batch update starting at', i); - await _volatilityOracle.batchUpdateFixedTimedelta(BATCH_SIZE); - } else { - console.log('batch update starting at', i); - await _volatilityOracle.batchUpdateFast(BATCH_SIZE); - } - } - console.log('Length:', i); - return _volatilityOracle; - }; - - beforeEach('create a full volatilityOracle', async () => { - volatilityOracle = await loadFixture(maxedOutVolatilityOracleFixture); - }); - - it('index wrapped around', async () => { - expect(await volatilityOracle.index()).to.eq(163); - }); - - async function checkGetPoints(secondsAgo: number, expected?: { tickCumulative: BigNumberish }) { - const { tickCumulatives } = await volatilityOracle.getTimepoints([secondsAgo]); - const check = { - tickCumulative: tickCumulatives[0].toString(), - }; - if (typeof expected === 'undefined') { - expect(check).to.matchSnapshot(); - } else { - expect(check).to.deep.eq({ - tickCumulative: expected.tickCumulative.toString(), - }); - } - } - - it('can getTimepoints into the ordered portion with exact seconds ago', async () => { - await checkGetPoints(100 * step, { - tickCumulative: '-2151581601', - }); - }); - - it('can getTimepoints into the ordered portion', async () => { - await checkGetPoints(100 * step + 5, { - tickCumulative: '-2151253621', - }); - }); - - it('can getTimepoints at exactly the latest timepoint', async () => { - await checkGetPoints(0, { - tickCumulative: '-2158146451', - }); - }); - - it('can getTimepoints at exactly the latest timepoint after some time passes', async () => { - await volatilityOracle.advanceTime(5); - await checkGetPoints(5, { - tickCumulative: '-2158146451', - }); - }); - - it('can getTimepoints after the latest timepoint counterfactual', async () => { - await volatilityOracle.advanceTime(5); - await checkGetPoints(3, { - tickCumulative: '-2158277849', - }); - }); - - it('can getTimepoints into the unordered portion of array at exact seconds ago of timepoint', async () => { - await checkGetPoints(200 * step, { - tickCumulative: '-2145026751', - }); - }); - - it('can getTimepoints the oldest timepoint 65534 seconds ago', async () => { - await checkGetPoints(step * 65534, { - tickCumulative: '-13530', - }); - }); - - it('can getTimepoints the oldest timepoint 65534 + 5 seconds ago if time has elapsed', async () => { - await volatilityOracle.advanceTime(5); - await checkGetPoints(step * 65534 + 5, { - tickCumulative: '-13530', - }); - }); - - describe('#getAverageVolatility', () => { - it('last timepoint is target', async () => { - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(360563298); - }); - - it('target is after last timepoint', async () => { - await volatilityOracle.advanceTime(10); - const volatility = await volatilityOracle.getAverageVolatility(); - expect(volatility).to.be.eq(360672090); - }); - }); - - it('gas cost of getTimepoints(0) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - it(`gas cost of getTimepoints(200 * ${step}) [ @skip-on-coverage ]`, async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([200 * step])); - }); - it(`gas cost of getTimepoints(200 * ${step} + 5) [ @skip-on-coverage ]`, async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([200 * step + 5])); - }); - it('gas cost of getTimepoints(0) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([0])); - }); - it('gas cost of getTimepoints(5) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([5])); - }); - it('gas cost of getTimepoints(middle) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([(65534 / 2) * step])); - }); - it('gas cost of getTimepoints(oldest) [ @skip-on-coverage ]', async () => { - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([65534 * step])); - }); - it('gas cost of getTimepoints(oldest) after 5 seconds [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(5); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([65534 * step + 5])); - }); - it('gas cost of getTimepoints(24h ago) after 12 hours [ @skip-on-coverage ]', async () => { - await volatilityOracle.advanceTime(12 * 60 * 60); - await snapshotGasCost(volatilityOracle.getGasCostOfGetPoints([24 * 60 * 60])); - }); - }); -}); diff --git a/src/plugin/test/__snapshots__/AdaptiveFee.spec.ts.snap b/src/plugin/test/__snapshots__/AdaptiveFee.spec.ts.snap deleted file mode 100644 index eac7392e6..000000000 --- a/src/plugin/test/__snapshots__/AdaptiveFee.spec.ts.snap +++ /dev/null @@ -1,48 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AdaptiveFee #getFee fee grid snapshot 1`] = ` -"[Volt: 0] Fee:100 Correct: 100 Error: 0.00% -[Volt: 25] Fee:109 Correct: 109.88566020116735 Error: -0.81% -[Volt: 50] Fee:115 Correct: 115.07468734318519 Error: -0.06% -[Volt: 75] Fee:122 Correct: 122.9658142057126 Error: -0.79% -[Volt: 100] Fee:134 Correct: 134.9376736874884 Error: -0.69% -[Volt: 112] Fee:142 Correct: 142.70183650179465 Error: -0.49% -[Volt: 125] Fee:152 Correct: 153.03530578674327 Error: -0.68% -[Volt: 150] Fee:179 Correct: 180.24490575058215 Error: -0.69% -[Volt: 175] Fee:220 Correct: 220.82186928039468 Error: -0.37% -[Volt: 200] Fee:279 Correct: 280.6029635235478 Error: -0.57% -[Volt: 250] Fee:486 Correct: 489.1516735854751 Error: -0.64% -[Volt: 300] Fee:870 Correct: 870.304074017156 Error: -0.03% -[Volt: 325] Fee:1126 Correct: 1132.099271807873 Error: -0.54% -[Volt: 350] Fee:1427 Correct: 1427.4119730278996 Error: -0.03% -[Volt: 359] Fee:1537 Correct: 1537.7121585699178 Error: -0.05% -[Volt: 360] Fee:1550 Correct: 1550 Error: 0.00% -[Volt: 375] Fee:1733 Correct: 1733.3355803968775 Error: -0.02% -[Volt: 400] Fee:2029 Correct: 2023.525343786886 Error: 0.27% -[Volt: 500] Fee:2752 Correct: 2752.733988897282 Error: -0.03% -[Volt: 525] Fee:2834 Correct: 2833.227869910604 Error: 0.03% -[Volt: 800] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 1000] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 1500] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 2000] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 3000] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 5000] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 8000] Fee:3000 Correct: 3000 Error: 0.00% -[Volt: 10000] Fee:3033 Correct: 3033.3655740185636 Error: -0.01% -[Volt: 20000] Fee:3107 Correct: 3107.5309030862936 Error: -0.02% -[Volt: 50000] Fee:5828 Correct: 5828.2486461390345 Error: -0.00% -[Volt: 60000] Fee:9000 Correct: 9000 Error: 0.00% -[Volt: 80000] Fee:13957 Correct: 13958.012690048654 Error: -0.01% -[Volt: 100000] Fee:14892 Correct: 14892.469096913706 Error: -0.00% -Mean error: -0.19% -Max error: 0.27% - -====================================== -" -`; - -exports[`AdaptiveFee #getFee gas cost [ @skip-on-coverage ] gas cost of 0 volatility 1`] = `548`; - -exports[`AdaptiveFee #getFee gas cost [ @skip-on-coverage ] gas cost of 100 volatility 1`] = `548`; - -exports[`AdaptiveFee #getFee gas cost [ @skip-on-coverage ] gas cost of 2000 volatility 1`] = `1272`; diff --git a/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap deleted file mode 100644 index f1a709f59..000000000 --- a/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap +++ /dev/null @@ -1,123 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AlgebraBasePluginV1 #DynamicFeeManager #adaptiveFee single huge spike after day 1`] = ` -Array [ - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 124 ", - "Fee: 124 ", - "Fee: 124 ", - "Fee: 124 ", - "Fee: 100 ", -] -`; - -exports[`AlgebraBasePluginV1 #DynamicFeeManager #adaptiveFee single huge spike after initialization 1`] = ` -Array [ - "Fee: 3714 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 2965 ", - "Fee: 2602 ", - "Fee: 1733 ", - "Fee: 995 ", - "Fee: 607 ", - "Fee: 411 ", - "Fee: 309 ", - "Fee: 250 ", - "Fee: 215 ", - "Fee: 191 ", - "Fee: 174 ", - "Fee: 163 ", - "Fee: 154 ", - "Fee: 147 ", - "Fee: 142 ", - "Fee: 139 ", - "Fee: 135 ", - "Fee: 132 ", - "Fee: 130 ", - "Fee: 128 ", - "Fee: 126 ", - "Fee: 100 ", -] -`; - -exports[`AlgebraBasePluginV1 #DynamicFeeManager #adaptiveFee single huge step after day 1`] = ` -Array [ - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 100 ", -] -`; - -exports[`AlgebraBasePluginV1 #DynamicFeeManager #adaptiveFee single huge step after initialization 1`] = ` -Array [ - "Fee: 15000 ", - "Fee: 15000 ", - "Fee: 15000 ", - "Fee: 15000 ", - "Fee: 14311 ", - "Fee: 11402 ", - "Fee: 7651 ", - "Fee: 5386 ", - "Fee: 4312 ", - "Fee: 3795 ", - "Fee: 3525 ", - "Fee: 3371 ", - "Fee: 3277 ", - "Fee: 3216 ", - "Fee: 3174 ", - "Fee: 3144 ", - "Fee: 3123 ", - "Fee: 3106 ", - "Fee: 3093 ", - "Fee: 3083 ", - "Fee: 3075 ", - "Fee: 3068 ", - "Fee: 3062 ", - "Fee: 3058 ", - "Fee: 3037 ", -] -`; - -exports[`AlgebraBasePluginV1 AlgebraBasePluginV1 external methods #changeFeeConfiguration feeConfig getter gas cost [ @skip-on-coverage ] 1`] = `23708`; diff --git a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap deleted file mode 100644 index 5d145f367..000000000 --- a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ /dev/null @@ -1,143 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee large swap crossing several initialized ticks 1`] = `216359`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `166064`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `201502`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `194288`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `164866`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `215130`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `164877`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `198342`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `209276`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `164349`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `118220`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `112300`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `117100`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `128099`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `116708`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `121508`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `127656`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `112961`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `117761`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #collect close to worst case 1`] = `52593`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #collect close to worst case, two tokens 1`] = `70312`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint above current price add to position existing 1`] = `128469`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint above current price new position mint first in range 1`] = `282332`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint above current price second position in same range 1`] = `145569`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint around current price add to position existing 1`] = `153538`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint around current price new position mint first in range 1`] = `360446`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint around current price second position in same range 1`] = `170638`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below current price add to position existing 1`] = `129037`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below current price new position mint first in range 1`] = `356677`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below current price second position in same range 1`] = `146137`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #poke best case 1`] = `63711`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `157775`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `157744`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `157220`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `174335`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `207885`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `157842`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `207885`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `227085`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `128008`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `127972`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `143753`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `178124`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `234847`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `165375`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `157836`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `157784`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `128033`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `189186`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `189134`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `142283`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `166586`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `166555`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `157469`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `183383`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `217644`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `166653`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `217644`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `236844`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `136819`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `136783`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `152801`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `187883`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `244606`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `165624`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `166658`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `166606`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `136855`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `198008`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `197956`; - -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `151105`; diff --git a/src/plugin/test/__snapshots__/OracleLibrary.spec.ts.snap b/src/plugin/test/__snapshots__/OracleLibrary.spec.ts.snap deleted file mode 100644 index 48c52fdca..000000000 --- a/src/plugin/test/__snapshots__/OracleLibrary.spec.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OracleLibrary #consult gas test [ @skip-on-coverage ] 1`] = `12255`; - -exports[`OracleLibrary #getQuoteAtTick gas test [ @skip-on-coverage ] 1`] = `1134`; diff --git a/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap b/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap deleted file mode 100644 index 08bddc64a..000000000 --- a/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SlidingFee #getFee gas cost [ @skip-on-coverage ] gas cost of same tick 1`] = `26776`; - -exports[`SlidingFee #getFee gas cost [ @skip-on-coverage ] gas cost of tick decrease 1`] = `31875`; - -exports[`SlidingFee #getFee gas cost [ @skip-on-coverage ] gas cost of tick increase 1`] = `31766`; diff --git a/src/plugin/test/__snapshots__/VolatilityOracle.spec.ts.snap b/src/plugin/test/__snapshots__/VolatilityOracle.spec.ts.snap deleted file mode 100644 index a1c05ceea..000000000 --- a/src/plugin/test/__snapshots__/VolatilityOracle.spec.ts.snap +++ /dev/null @@ -1,101 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VolatilityOracle #getTimepoints before initialization gas for getTimepoints since most recent [ @skip-on-coverage ] 1`] = `8271`; - -exports[`VolatilityOracle #getTimepoints before initialization gas for single timepoint at current time [ @skip-on-coverage ] 1`] = `6327`; - -exports[`VolatilityOracle #getTimepoints before initialization gas for single timepoint at current time after some time [ @skip-on-coverage ] 1`] = `8137`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 fetch many values 1`] = ` -Object { - "tickCumulatives": Array [ - "-13", - "-31", - "-43", - "-37", - "-15", - "9", - "15", - ], -} -`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 gas all of last 20 seconds [ @skip-on-coverage ] 1`] = `100743`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 gas between oldest and oldest + 1 [ @skip-on-coverage ] 1`] = `17966`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 gas latest equal [ @skip-on-coverage ] 1`] = `6327`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 gas latest transform [ @skip-on-coverage ] 1`] = `10137`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 gas middle [ @skip-on-coverage ] 1`] = `17453`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 5 gas oldest [ @skip-on-coverage ] 1`] = `17030`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 fetch many values 1`] = ` -Object { - "tickCumulatives": Array [ - "-13", - "-31", - "-43", - "-37", - "-15", - "9", - "15", - ], -} -`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 gas all of last 20 seconds [ @skip-on-coverage ] 1`] = `100395`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 gas between oldest and oldest + 1 [ @skip-on-coverage ] 1`] = `17908`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 gas latest equal [ @skip-on-coverage ] 1`] = `6327`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 gas latest transform [ @skip-on-coverage ] 1`] = `10166`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 gas middle [ @skip-on-coverage ] 1`] = `17424`; - -exports[`VolatilityOracle #getTimepoints initialized with 5 timepoints with starting time of 4294967291 gas oldest [ @skip-on-coverage ] 1`] = `16972`; - -exports[`VolatilityOracle #initialize gas [ @skip-on-coverage ] 1`] = `50443`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(0) [ @skip-on-coverage ] 1`] = `6335`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(0) after 5 seconds [ @skip-on-coverage ] 1`] = `16887`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(5) after 5 seconds [ @skip-on-coverage ] 1`] = `6469`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(24h ago) [ @skip-on-coverage ] 1`] = `13127`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(24h ago) after 5 seconds [ @skip-on-coverage ] 1`] = `14592`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(24h ago) after 15 minutes [ @skip-on-coverage ] 1`] = `54263`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(200 * 13 + 5) [ @skip-on-coverage ] 1`] = `49033`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(200 * 13) [ @skip-on-coverage ] 1`] = `48978`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(middle) [ @skip-on-coverage ] 1`] = `12476`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(oldest) [ @skip-on-coverage ] 1`] = `47091`; - -exports[`VolatilityOracle full volatilityOracle gas cost of getTimepoints(oldest) after 5 seconds [ @skip-on-coverage ] 1`] = `47091`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(0) [ @skip-on-coverage ] 1`] = `6335`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(0) after 5 seconds [ @skip-on-coverage ] 1`] = `8174`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(5) after 5 seconds [ @skip-on-coverage ] 1`] = `6469`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(24h ago) after 12 hours [ @skip-on-coverage ] 1`] = `69074`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(200 * 1 + 5) [ @skip-on-coverage ] 1`] = `71383`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(200 * 1) [ @skip-on-coverage ] 1`] = `65785`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(middle) [ @skip-on-coverage ] 1`] = `12516`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(oldest) [ @skip-on-coverage ] 1`] = `47131`; - -exports[`VolatilityOracle full volatilityOracle, maximal density gas cost of getTimepoints(oldest) after 5 seconds [ @skip-on-coverage ] 1`] = `47131`; diff --git a/src/plugin/test/shared/checkTimepointEquals.ts b/src/plugin/test/shared/checkTimepointEquals.ts deleted file mode 100644 index 442effe38..000000000 --- a/src/plugin/test/shared/checkTimepointEquals.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect } from './expect'; - -// helper function because we cannot do a simple deep equals with the -// timepoint result object returned from ethers because it extends array -export default function checkTimepointEquals( - { - initialized, - tickCumulative, - blockTimestamp, - }: { - initialized: boolean; - tickCumulative: bigint; - blockTimestamp: bigint; - }, - expected: { - initialized: boolean; - tickCumulative: bigint; - blockTimestamp: bigint; - } -) { - expect( - { - initialized, - blockTimestamp, - tickCumulative: tickCumulative.toString(), - }, - `timepoint is equivalent` - ).to.deep.eq({ - ...expected, - tickCumulative: expected.tickCumulative.toString(), - }); -} diff --git a/src/plugin/test/shared/expect.ts b/src/plugin/test/shared/expect.ts deleted file mode 100644 index 0de1986a1..000000000 --- a/src/plugin/test/shared/expect.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, use } from 'chai'; -import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'; - -use(jestSnapshotPlugin()); - -export { expect }; diff --git a/src/plugin/test/shared/externalFixtures.ts b/src/plugin/test/shared/externalFixtures.ts deleted file mode 100644 index 742ac919a..000000000 --- a/src/plugin/test/shared/externalFixtures.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - abi as FACTORY_ABI, - bytecode as FACTORY_BYTECODE, -} from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraFactory.sol/AlgebraFactory.json'; -import { - abi as TEST_CALLEE_ABI, - bytecode as TEST_CALLEE_BYTECODE, -} from '@cryptoalgebra/integral-core/artifacts/contracts/test/TestAlgebraCallee.sol/TestAlgebraCallee.json'; -import { - abi as POOL_DEPLOYER_ABI, - bytecode as POOL_DEPLOYER_BYTECODE, -} from '@cryptoalgebra/integral-core/artifacts/contracts/test/MockTimeAlgebraPoolDeployer.sol/MockTimeAlgebraPoolDeployer.json'; -import { - abi as POOL_ABI, - bytecode as POOL_BYTECODE, -} from '@cryptoalgebra/integral-core/artifacts/contracts/test/MockTimeAlgebraPool.sol/MockTimeAlgebraPool.json'; -import { - abi as COM_VAULT_DEPLOYER_ABI, - bytecode as COM_VAULT_DEPLOYER_BYTECODE, -} from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraCommunityVault.sol/AlgebraCommunityVault.json'; -import { - abi as COM_VAULT_STUB_DEPLOYER_ABI, - bytecode as COM_VAULT_STUB_DEPLOYER_BYTECODE, -} from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraVaultFactoryStub.sol/AlgebraVaultFactoryStub.json'; - -import { ethers } from 'hardhat'; -import { - MockTimeAlgebraPoolDeployer, - AlgebraCommunityVault, - TestAlgebraCallee, - MockTimeAlgebraPool, - AlgebraFactory, - TestERC20, -} from '@cryptoalgebra/integral-core/typechain'; -import { getCreateAddress } from 'ethers'; -import { ZERO_ADDRESS } from './fixtures'; - -interface TokensFixture { - token0: TestERC20; - token1: TestERC20; -} - -export async function tokensFixture(): Promise { - const tokenFactory = await ethers.getContractFactory('TestERC20'); - const tokenA = (await tokenFactory.deploy(2n ** 255n)) as any as TestERC20 & { address: string }; - const tokenB = (await tokenFactory.deploy(2n ** 255n)) as any as TestERC20 & { address: string }; - - tokenA.address = await tokenA.getAddress(); - tokenB.address = await tokenB.getAddress(); - - const [token0, token1] = [tokenA, tokenB].sort((_tokenA, _tokenB) => (_tokenA.address.toLowerCase() < _tokenB.address.toLowerCase() ? -1 : 1)); - - return { token0, token1 }; -} - -interface MockPoolDeployerFixture extends TokensFixture { - poolDeployer: MockTimeAlgebraPoolDeployer; - swapTargetCallee: TestAlgebraCallee; - factory: AlgebraFactory; - createPool(firstToken?: TestERC20, secondToken?: TestERC20): Promise; -} -export const algebraPoolDeployerMockFixture: () => Promise = async () => { - const { token0, token1 } = await tokensFixture(); - - const [deployer] = await ethers.getSigners(); - // precompute - const poolDeployerAddress = getCreateAddress({ - from: deployer.address, - nonce: (await ethers.provider.getTransactionCount(deployer.address)) + 1, - }); - - const factoryFactory = await ethers.getContractFactory(FACTORY_ABI, FACTORY_BYTECODE); - const factory = (await factoryFactory.deploy(poolDeployerAddress)) as any as AlgebraFactory; - - const poolDeployerFactory = await ethers.getContractFactory(POOL_DEPLOYER_ABI, POOL_DEPLOYER_BYTECODE); - const poolDeployer = (await poolDeployerFactory.deploy()) as any as MockTimeAlgebraPoolDeployer; - - const ADMIN_ROLE = await factory.POOLS_ADMINISTRATOR_ROLE(); - await factory.grantRole(ADMIN_ROLE, poolDeployer); - - const vaultFactory = await ethers.getContractFactory(COM_VAULT_DEPLOYER_ABI, COM_VAULT_DEPLOYER_BYTECODE); - const vault = (await vaultFactory.deploy(factory, deployer.address)) as any as AlgebraCommunityVault; - - const vaultFactoryStubFactory = await ethers.getContractFactory(COM_VAULT_STUB_DEPLOYER_ABI, COM_VAULT_STUB_DEPLOYER_BYTECODE); - const vaultFactoryStub = await vaultFactoryStubFactory.deploy(vault); - - await factory.setVaultFactory(vaultFactoryStub); - - const calleeContractFactory = await ethers.getContractFactory(TEST_CALLEE_ABI, TEST_CALLEE_BYTECODE); - const swapTargetCallee = (await calleeContractFactory.deploy()) as any as TestAlgebraCallee; - - const MockTimeAlgebraPoolFactory = await ethers.getContractFactory(POOL_ABI, POOL_BYTECODE); - - return { - poolDeployer, - swapTargetCallee, - token0, - token1, - factory, - createPool: async (firstToken = token0, secondToken = token1) => { - await poolDeployer.deployMock(factory, firstToken, secondToken); - - const sortedTokens = - BigInt(await firstToken.getAddress()) < BigInt(await secondToken.getAddress()) - ? [await firstToken.getAddress(), await secondToken.getAddress()] - : [await secondToken.getAddress(), await firstToken.getAddress()]; - const poolAddress = await poolDeployer.computeAddress(sortedTokens[0], sortedTokens[1]); - return MockTimeAlgebraPoolFactory.attach(poolAddress) as any as MockTimeAlgebraPool; - }, - }; -}; diff --git a/src/plugin/test/shared/fixtures.ts b/src/plugin/test/shared/fixtures.ts deleted file mode 100644 index 2e464009b..000000000 --- a/src/plugin/test/shared/fixtures.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { ethers } from 'hardhat'; -import { MockFactory, MockPool, MockTimeAlgebraBasePluginV1, MockTimeAlgebraBasePluginV2, MockTimeDSFactoryV2, MockTimeDSFactory, BasePluginV1Factory, BasePluginV2Factory } from '../../typechain'; - -type Fixture = () => Promise; -interface MockFactoryFixture { - mockFactory: MockFactory; -} -export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; - -async function mockFactoryFixture(): Promise { - const mockFactoryFactory = await ethers.getContractFactory('MockFactory'); - const mockFactory = (await mockFactoryFactory.deploy()) as any as MockFactory; - - return { mockFactory }; -} - -interface PluginFixture extends MockFactoryFixture { - plugin: MockTimeAlgebraBasePluginV1 | MockTimeAlgebraBasePluginV2; - mockPluginFactory: MockTimeDSFactory | MockTimeDSFactoryV2; - mockPool: MockPool; -} - -// Monday, October 5, 2020 9:00:00 AM GMT-05:00 -export const TEST_POOL_START_TIME = 1601906400; -export const TEST_POOL_DAY_BEFORE_START = 1601906400 - 24 * 60 * 60; - -export const pluginFixture: Fixture = async function (): Promise { - const { mockFactory } = await mockFactoryFixture(); - //const { token0, token1, token2 } = await tokensFixture() - - const mockPluginFactoryFactory = await ethers.getContractFactory('MockTimeDSFactory'); - const mockPluginFactory = (await mockPluginFactoryFactory.deploy(mockFactory)) as any as MockTimeDSFactory; - - const mockPoolFactory = await ethers.getContractFactory('MockPool'); - const mockPool = (await mockPoolFactory.deploy()) as any as MockPool; - - await mockPluginFactory.beforeCreatePoolHook(mockPool, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x'); - const pluginAddress = await mockPluginFactory.pluginByPool(mockPool); - - const mockDSOperatorFactory = await ethers.getContractFactory('MockTimeAlgebraBasePluginV1'); - const plugin = mockDSOperatorFactory.attach(pluginAddress) as any as MockTimeAlgebraBasePluginV1; - - return { - plugin, - mockPluginFactory, - mockPool, - mockFactory, - }; -}; - -interface PluginFactoryFixture extends MockFactoryFixture { - pluginFactory: BasePluginV1Factory | BasePluginV2Factory; -} - -export const pluginFactoryFixture: Fixture = async function (): Promise { - const { mockFactory } = await mockFactoryFixture(); - - const pluginFactoryFactory = await ethers.getContractFactory('BasePluginV1Factory'); - const pluginFactory = (await pluginFactoryFactory.deploy(mockFactory)) as any as BasePluginV1Factory; - - return { - pluginFactory, - mockFactory, - }; -}; - -export const pluginFactoryFixtureV2: Fixture = async function (): Promise { - const { mockFactory } = await mockFactoryFixture(); - - const pluginFactoryFactory = await ethers.getContractFactory('BasePluginV2Factory'); - const pluginFactory = (await pluginFactoryFactory.deploy(mockFactory)) as any as BasePluginV2Factory; - - return { - pluginFactory, - mockFactory, - }; -}; - - -export const pluginFixtureV2: Fixture = async function (): Promise { - const { mockFactory } = await mockFactoryFixture(); - //const { token0, token1, token2 } = await tokensFixture() - - const mockPluginFactoryFactory = await ethers.getContractFactory('MockTimeDSFactoryV2'); - const mockPluginFactory = (await mockPluginFactoryFactory.deploy(mockFactory)) as any as MockTimeDSFactoryV2; - - const mockPoolFactory = await ethers.getContractFactory('MockPool'); - const mockPool = (await mockPoolFactory.deploy()) as any as MockPool; - - await mockPluginFactory.beforeCreatePoolHook(mockPool, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, '0x'); - const pluginAddress = await mockPluginFactory.pluginByPool(mockPool); - - const mockDSOperatorFactory = await ethers.getContractFactory('MockTimeAlgebraBasePluginV2'); - const plugin = mockDSOperatorFactory.attach(pluginAddress) as any as MockTimeAlgebraBasePluginV2; - - return { - plugin, - mockPluginFactory, - mockPool, - mockFactory, - }; -}; \ No newline at end of file diff --git a/src/plugin/test/shared/format.ts b/src/plugin/test/shared/format.ts deleted file mode 100644 index c17493436..000000000 --- a/src/plugin/test/shared/format.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Decimal } from 'decimal.js'; -import { BigNumberish } from 'ethers'; - -export function formatTokenAmount(num: BigNumberish): string { - return new Decimal(num.toString()).dividedBy(new Decimal(10).pow(18)).toPrecision(5); -} - -export function formatPrice(price: BigNumberish): string { - return new Decimal(price.toString()).dividedBy(new Decimal(2).pow(96)).pow(2).toPrecision(5); -} diff --git a/src/plugin/test/shared/snapshotGasCost.ts b/src/plugin/test/shared/snapshotGasCost.ts deleted file mode 100644 index d880f645c..000000000 --- a/src/plugin/test/shared/snapshotGasCost.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TransactionReceipt, TransactionResponse } from '@ethersproject/abstract-provider'; -import { expect } from './expect'; -import { Contract, ContractTransactionResponse } from 'ethers'; - -export default async function snapshotGasCost( - x: - | TransactionResponse - | Promise - | ContractTransactionResponse - | Promise - | TransactionReceipt - | Promise - | BigInt - | Contract - | Promise -): Promise { - const resolved = await x; - if (typeof resolved == 'bigint' || resolved instanceof Number || typeof resolved == 'number') { - expect(Number(resolved)).toMatchSnapshot(); - } else if ('deployTransaction' in resolved) { - const receipt = await resolved.deployTransaction.wait(); - expect(receipt.gasUsed.toNumber()).toMatchSnapshot(); - } else if ('wait' in resolved) { - const waited = await resolved.wait(); - expect(Number(waited.gasUsed)).toMatchSnapshot(); - } -} diff --git a/src/plugin/test/shared/utilities.ts b/src/plugin/test/shared/utilities.ts deleted file mode 100644 index 58de13288..000000000 --- a/src/plugin/test/shared/utilities.ts +++ /dev/null @@ -1,70 +0,0 @@ -import bn from 'bignumber.js'; -import { BigNumberish, AbiCoder, keccak256, getAddress } from 'ethers'; - -export const MaxUint128 = 2n ** 128n - 1n; - -export const MIN_TICK = -887272; -export const MAX_TICK = -MIN_TICK; - -export const getMinTick = (tickSpacing: number) => Math.ceil(-887272 / tickSpacing) * tickSpacing; -export const getMaxTick = (tickSpacing: number) => Math.floor(887272 / tickSpacing) * tickSpacing; - -export const getMaxLiquidityPerTick = (tickSpacing: number) => - (2n ** 128n - 1n) / (BigInt(getMaxTick(tickSpacing) - getMinTick(tickSpacing)) / BigInt(tickSpacing) + 1n); - -export const MIN_SQRT_RATIO = BigInt('4295128739'); -export const MAX_SQRT_RATIO = BigInt('1461446703485210103287273052203988822378723970342'); - -export enum FeeAmount { - LOW = 500, - MEDIUM = 3000, - HIGH = 10000, -} - -export const PLUGIN_FLAGS = { - BEFORE_SWAP_FLAG: 1, - AFTER_SWAP_FLAG: 1 << 1, - BEFORE_POSITION_MODIFY_FLAG: 1 << 2, - AFTER_POSITION_MODIFY_FLAG: 1 << 3, - BEFORE_FLASH_FLAG: 1 << 4, - AFTER_FLASH_FLAG: 1 << 5, - AFTER_INIT_FLAG: 1 << 6, - DYNAMIC_FEE: 1 << 7, -}; - -export const TICK_SPACINGS: { [amount in FeeAmount]: number } = { - [FeeAmount.LOW]: 10, - [FeeAmount.MEDIUM]: 60, - [FeeAmount.HIGH]: 100, -}; - -export function expandTo18Decimals(n: number): bigint { - return BigInt(n) * 10n ** 18n; -} - -export function getCreate2Address(factoryAddress: string, [tokenA, tokenB]: [string, string], bytecode: string): string { - const [token0, token1] = tokenA.toLowerCase() < tokenB.toLowerCase() ? [tokenA, tokenB] : [tokenB, tokenA]; - const constructorArgumentsEncoded = AbiCoder.defaultAbiCoder().encode(['address', 'address'], [token0, token1]); - const create2Inputs = [ - '0xff', - factoryAddress, - // salt - keccak256(constructorArgumentsEncoded), - // init code. bytecode + constructor arguments - keccak256(bytecode), - ]; - const sanitizedInputs = `0x${create2Inputs.map((i) => i.slice(2)).join('')}`; - return getAddress(`0x${keccak256(sanitizedInputs).slice(-40)}`); -} - -export function encodeCallback(address: string, paid0?: bigint, paid1?: bigint): string { - if (paid0) return AbiCoder.defaultAbiCoder().encode(['address', 'uint256', 'uint256'], [address, paid0, paid1]); - return AbiCoder.defaultAbiCoder().encode(['address'], [address]); -} - -bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 }); - -// returns the sqrt price as a 64x96 -export function encodePriceSqrt(reserve1: BigNumberish, reserve0: BigNumberish): bigint { - return BigInt(new bn(reserve1.toString()).div(reserve0.toString()).sqrt().multipliedBy(new bn(2).pow(96)).integerValue(3).toString()); -} diff --git a/src/plugin/tsconfig.json b/src/plugin/tsconfig.json deleted file mode 100644 index 0220a1a83..000000000 --- a/src/plugin/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "outDir": "dist", - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "incremental": true, - "typeRoots": ["./typechain", "./node_modules/@types", "../../node_modules/@types", "../../node_modules/@nomicfoundation"], - "types": ["hardhat-ethers", "hardhat-network-helpers", "hardhat-chai-matchers"] - }, - "include": ["./test/**/*.ts", "./hardhat.config.ts", "./scripts/**/*.ts"] -} From 57454c9abb79743e1a076e0f2d8daa97cf4f6220 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 14 Jan 2026 17:56:12 +0300 Subject: [PATCH 2/4] adapt farming to external plugin packages --- docs/postprocess.js | 1 - src/farming/contracts/FarmingCenter.sol | 2 +- .../farmings/AlgebraEternalFarming.sol | 2 +- .../interfaces/IAlgebraEternalVirtualPool.sol | 2 +- .../contracts/interfaces/IFarmingCenter.sol | 2 +- .../contracts/test/NftPosManagerMock.sol | 1 + src/farming/package-lock.json | 54 - src/farming/package.json | 97 +- src/farming/scripts/deploy.js | 6 - src/farming/test/helpers/index.ts | 1158 ++-- src/farming/test/shared/externalFixtures.ts | 2 +- src/farming/test/shared/fixtures.ts | 627 +-- src/farming/test/unit/EternalFarms.spec.ts | 4856 ++++++++--------- src/farming/test/unit/FarmingCenter.spec.ts | 1550 +++--- 14 files changed, 4151 insertions(+), 4209 deletions(-) delete mode 100644 src/farming/package-lock.json diff --git a/docs/postprocess.js b/docs/postprocess.js index 29b6a295d..de7922007 100644 --- a/docs/postprocess.js +++ b/docs/postprocess.js @@ -5,7 +5,6 @@ const remappings = [ ['=>', '=>'], ['../@cryptoalgebra/integral-core/contracts', '../Core'], ['../@cryptoalgebra/integral-periphery/contracts', '../Periphery'], - ['../@cryptoalgebra/integral-base-plugin/contracts', '../Plugin'], [/\([^\(]+@openzeppelin\/contracts[^\(]+\)/g, '(https://docs.openzeppelin.com/contracts/4.x/)'], ]; diff --git a/src/farming/contracts/FarmingCenter.sol b/src/farming/contracts/FarmingCenter.sol index fd60a6f3d..39556e480 100644 --- a/src/farming/contracts/FarmingCenter.sol +++ b/src/farming/contracts/FarmingCenter.sol @@ -7,7 +7,7 @@ import '@cryptoalgebra/integral-periphery/contracts/interfaces/IPositionFollower import '@cryptoalgebra/integral-periphery/contracts/interfaces/INonfungiblePositionManager.sol'; import '@cryptoalgebra/integral-periphery/contracts/base/Multicall.sol'; import '@cryptoalgebra/integral-periphery/contracts/libraries/PoolAddress.sol'; -import '@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IFarmingPlugin.sol'; +import '@cryptoalgebra/farming-proxy-plugin/contracts/interfaces/IFarmingPlugin.sol'; import './interfaces/IFarmingCenter.sol'; import './libraries/IncentiveId.sol'; diff --git a/src/farming/contracts/farmings/AlgebraEternalFarming.sol b/src/farming/contracts/farmings/AlgebraEternalFarming.sol index 54381f914..52efc1fd0 100644 --- a/src/farming/contracts/farmings/AlgebraEternalFarming.sol +++ b/src/farming/contracts/farmings/AlgebraEternalFarming.sol @@ -13,7 +13,7 @@ import '@cryptoalgebra/integral-core/contracts/libraries/LowGasSafeMath.sol'; import '@cryptoalgebra/integral-periphery/contracts/libraries/TransferHelper.sol'; -import '@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IFarmingPlugin.sol'; +import '@cryptoalgebra/farming-proxy-plugin/contracts/interfaces/IFarmingPlugin.sol'; import '../interfaces/IAlgebraEternalFarming.sol'; import '../interfaces/IAlgebraEternalVirtualPool.sol'; diff --git a/src/farming/contracts/interfaces/IAlgebraEternalVirtualPool.sol b/src/farming/contracts/interfaces/IAlgebraEternalVirtualPool.sol index 73d546919..3bdea97d9 100644 --- a/src/farming/contracts/interfaces/IAlgebraEternalVirtualPool.sol +++ b/src/farming/contracts/interfaces/IAlgebraEternalVirtualPool.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.8.4; -import '@cryptoalgebra/integral-base-plugin/contracts/interfaces/IAlgebraVirtualPool.sol'; +import '@cryptoalgebra/farming-proxy-plugin/contracts/interfaces/IAlgebraVirtualPool.sol'; /// @title Algebra eternal virtual pool interface /// @notice Used to track active liquidity in farming and distribute rewards diff --git a/src/farming/contracts/interfaces/IFarmingCenter.sol b/src/farming/contracts/interfaces/IFarmingCenter.sol index 0ffe17245..a031ed1b8 100644 --- a/src/farming/contracts/interfaces/IFarmingCenter.sol +++ b/src/farming/contracts/interfaces/IFarmingCenter.sol @@ -7,7 +7,7 @@ import '@cryptoalgebra/integral-core/contracts/interfaces/IERC20Minimal.sol'; import '@cryptoalgebra/integral-periphery/contracts/interfaces/IMulticall.sol'; import '@cryptoalgebra/integral-periphery/contracts/interfaces/INonfungiblePositionManager.sol'; -import '@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IFarmingPlugin.sol'; +import '@cryptoalgebra/farming-proxy-plugin/contracts/interfaces/IFarmingPlugin.sol'; import '../base/IncentiveKey.sol'; import '../interfaces/IAlgebraEternalFarming.sol'; diff --git a/src/farming/contracts/test/NftPosManagerMock.sol b/src/farming/contracts/test/NftPosManagerMock.sol index d3dd55ee9..f8ce61637 100644 --- a/src/farming/contracts/test/NftPosManagerMock.sol +++ b/src/farming/contracts/test/NftPosManagerMock.sol @@ -3,6 +3,7 @@ pragma solidity =0.8.20; import '../interfaces/IAlgebraEternalVirtualPool.sol'; import '@cryptoalgebra/integral-periphery/contracts/interfaces/IPositionFollower.sol'; +import '@cryptoalgebra/default-plugin/contracts/interfaces/IAlgebraDefaultPluginFactory.sol'; /// @dev Test contract for virtual pool onlyPool methods contract NftPosManagerMock { diff --git a/src/farming/package-lock.json b/src/farming/package-lock.json deleted file mode 100644 index e54c18e91..000000000 --- a/src/farming/package-lock.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@cryptoalgebra/integral-farming", - "version": "1.2.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@cryptoalgebra/integral-farming", - "version": "1.2.1", - "license": "GPL-3.0-or-later", - "dependencies": { - "@cryptoalgebra/integral-core": "1.2.1", - "@openzeppelin/contracts": "4.9.3" - }, - "devDependencies": { - "@types/lodash": "^4.14.170", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@cryptoalgebra/integral-core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.2.1.tgz", - "integrity": "sha512-+Lj7y2ZqpJ58fNtqTRvrMv/HPcyqNkMpINu9LQK6/iMDePmzNoywogLLiGU34MlxAaGEwO6XNWM7ENq/YzzZzA==", - "dependencies": { - "@openzeppelin/contracts": "4.9.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@openzeppelin/contracts": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.3.tgz", - "integrity": "sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==" - }, - "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", - "dev": true - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - } - } -} diff --git a/src/farming/package.json b/src/farming/package.json index 0b6b48836..f6e773f04 100644 --- a/src/farming/package.json +++ b/src/farming/package.json @@ -1,49 +1,50 @@ -{ - "name": "@cryptoalgebra/integral-farming", - "description": "Liquidity mining contracts for Algebra Integral protocol", - "license": "GPL-3.0-or-later", - "version": "1.2.4", - "publishConfig": { - "access": "public" - }, - "keywords": [ - "algebra", - "liquidity mining" - ], - "repository": { - "type": "git", - "url": "https://github.com/cryptoalgebra/Algebra" - }, - "files": [ - "contracts", - "!contracts/test", - "artifacts/contracts/**/*.json", - "!artifacts/contracts/**/*.dbg.json", - "!artifacts/contracts/test/**/*", - "!artifacts/contracts/base/**/*" - ], - "dependencies": { - "@openzeppelin/contracts": "4.9.3", - "@cryptoalgebra/integral-core": "~1.2.7", - "@cryptoalgebra/integral-periphery": "~1.2.4", - "@cryptoalgebra/integral-base-plugin": "~1.2.4" - }, - "devDependencies": { - "@types/lodash": "^4.14.170", - "lodash": "^4.17.21" - }, - "scripts": { - "compile": "npm --prefix ../periphery run compile && npm --prefix ../plugin run compile && hardhat compile", - "lint": "eslint . --ext .ts", - "size-contracts": "hardhat compile && hardhat size-contracts", - "solhint": "solhint --config ../../.solhint.json \"contracts/**/*.sol\"", - "test": "hardhat test --parallel", - "clear-cache": "rm -rf artifacts/ cache/ typechain/", - "coverage": "npm --prefix ../periphery run compile && hardhat coverage", - "precommit": "pretty-quick --staged --pattern '**/*.sol'" - }, - "engines": { - "npm": ">=10.0.0", - "node": ">=18.18.0" - } +{ + "name": "@cryptoalgebra/integral-farming", + "description": "Liquidity mining contracts for Algebra Integral protocol", + "license": "GPL-3.0-or-later", + "version": "1.2.4", + "publishConfig": { + "access": "public" + }, + "keywords": [ + "algebra", + "liquidity mining" + ], + "repository": { + "type": "git", + "url": "https://github.com/cryptoalgebra/Algebra" + }, + "files": [ + "contracts", + "!contracts/test", + "artifacts/contracts/**/*.json", + "!artifacts/contracts/**/*.dbg.json", + "!artifacts/contracts/test/**/*", + "!artifacts/contracts/base/**/*" + ], + "dependencies": { + "@openzeppelin/contracts": "4.9.3", + "@cryptoalgebra/integral-core": "~1.2.7", + "@cryptoalgebra/integral-periphery": "~1.2.4", + "@cryptoalgebra/default-plugin": "2.1.0", + "@cryptoalgebra/farming-proxy-plugin": "2.1.0" + }, + "devDependencies": { + "@types/lodash": "^4.14.170", + "lodash": "^4.17.21" + }, + "scripts": { + "compile": "npm --prefix ../periphery run compile && hardhat compile", + "lint": "eslint . --ext .ts", + "size-contracts": "hardhat compile && hardhat size-contracts", + "solhint": "solhint --config ../../.solhint.json \"contracts/**/*.sol\"", + "test": "hardhat test --parallel", + "clear-cache": "rm -rf artifacts/ cache/ typechain/", + "coverage": "npm --prefix ../periphery run compile && hardhat coverage", + "precommit": "pretty-quick --staged --pattern '**/*.sol'" + }, + "engines": { + "npm": ">=10.0.0", + "node": ">=18.18.0" + } } \ No newline at end of file diff --git a/src/farming/scripts/deploy.js b/src/farming/scripts/deploy.js index 3c897b6c8..dd3d75dc8 100644 --- a/src/farming/scripts/deploy.js +++ b/src/farming/scripts/deploy.js @@ -1,7 +1,6 @@ const hre = require('hardhat') const fs = require('fs') const path = require('path') -const BasePluginV1FactoryComplied = require('@cryptoalgebra/integral-base-plugin/artifacts/contracts/BasePluginV1Factory.sol/BasePluginV1Factory.json'); async function main() { const deployDataPath = path.resolve(__dirname, '../../../deploys.json') @@ -26,11 +25,6 @@ async function main() { await (await AlgebraEternalFarming.setFarmingCenterAddress(FarmingCenter.target)).wait() console.log('Updated farming center address in eternal(incentive) farming') - const pluginFactory = await hre.ethers.getContractAt(BasePluginV1FactoryComplied.abi, deploysData.BasePluginV1Factory) - - await (await pluginFactory.setFarmingAddress(FarmingCenter.target)).wait() - console.log('Updated farming center address in plugin factory') - const posManager = await hre.ethers.getContractAt( 'INonfungiblePositionManager', deploysData.nonfungiblePositionManager diff --git a/src/farming/test/helpers/index.ts b/src/farming/test/helpers/index.ts index f4e7a09b4..d97510080 100644 --- a/src/farming/test/helpers/index.ts +++ b/src/farming/test/helpers/index.ts @@ -1,579 +1,579 @@ -import { Wallet, MaxUint256, Interface } from 'ethers'; -import { blockTimestamp, BNe18, FeeAmount, getCurrentTick, maxGas, encodePath, arrayWrap, getMinTick, getMaxTick, ZERO_ADDRESS } from '../shared/index'; -import _ from 'lodash'; -import { TestERC20, INonfungiblePositionManager, AlgebraEternalFarming, IAlgebraPool, TestIncentiveId, FarmingCenter } from '../../typechain'; -import abi from '../../artifacts/contracts/farmings/EternalVirtualPool.sol/EternalVirtualPool.json'; -import { HelperTypes } from './types'; -import { ActorFixture } from '../shared/actors'; -import { mintPosition } from '../shared/fixtures'; -import { ISwapRouter } from '@cryptoalgebra/integral-periphery/typechain'; -import { ethers } from 'hardhat'; -import { ContractParams } from '../../types/contractParams'; -import { TestContext } from '../types'; -import Decimal from 'decimal.js'; - -/*** - * HelperCommands is a utility that abstracts away lower-tier ethereum details - * so that we can focus on core business logic. - * - * Each helper function should be a `HelperTypes.CommandFunction` - */ -export class HelperCommands { - actors: ActorFixture; - provider: any; - eternalFarming: AlgebraEternalFarming; - nft: INonfungiblePositionManager; - router: ISwapRouter; - pool: IAlgebraPool; - testIncentiveId: TestIncentiveId; - farmingCenter: FarmingCenter; - - DEFAULT_INCENTIVE_DURATION = 2_000; - DEFAULT_CLAIM_DURATION = 1_000; - DEFAULT_LP_AMOUNT = BNe18(10); - DEFAULT_FEE_AMOUNT = FeeAmount.MEDIUM; - - constructor({ - provider, - eternalFarming, - nft, - router, - pool, - actors, - testIncentiveId, - farmingCenter, - }: { - provider: any; - eternalFarming: AlgebraEternalFarming; - farmingCenter: FarmingCenter; - nft: INonfungiblePositionManager; - router: ISwapRouter; - pool: IAlgebraPool; - actors: ActorFixture; - testIncentiveId: TestIncentiveId; - }) { - this.actors = actors; - this.provider = provider; - this.eternalFarming = eternalFarming; - this.nft = nft; - this.router = router; - this.pool = pool; - this.testIncentiveId = testIncentiveId; - this.farmingCenter = farmingCenter; - } - - static fromTestContext = (context: TestContext, actors: ActorFixture, provider: any): HelperCommands => { - return new HelperCommands({ - actors, - provider, - nft: context.nft, - router: context.router, - eternalFarming: context.eternalFarming, - pool: context.poolObj, - testIncentiveId: context.testIncentiveId, - farmingCenter: context.farmingCenter, - }); - }; - - /*** - * Creates a staking incentive owned by `incentiveCreator` for `totalReward` of `rewardToken` - * - * Side-Effects: - * Transfers `rewardToken` to `incentiveCreator` if they do not have sufficient balance. - */ - createIncentiveFlow: HelperTypes.CreateIncentive.Command = async (params) => { - const { nonce } = params; - - const incentiveCreator = this.actors.incentiveCreator(); - - const bal = await params.rewardToken.balanceOf(incentiveCreator.address); - const bonusBal = await params.bonusRewardToken.balanceOf(incentiveCreator.address); - - if (bal < params.totalReward) { - await params.rewardToken.transfer(incentiveCreator.address, params.totalReward); - } - - if (bonusBal < params.bonusReward) { - await params.bonusRewardToken.transfer(incentiveCreator.address, params.bonusReward); - } - - let txResult; - let virtualPoolAddress; - - await params.rewardToken.connect(incentiveCreator).approve(this.eternalFarming, params.totalReward); - await params.bonusRewardToken.connect(incentiveCreator).approve(this.eternalFarming, params.bonusReward); - - let pluginAddres = params.plugin; - if (!pluginAddres) { - const pool = (await ethers.getContractAt('IAlgebraPool', params.poolAddress)) as any as IAlgebraPool; - pluginAddres = await pool.connect(incentiveCreator).plugin(); - } - - txResult = await (this.eternalFarming as AlgebraEternalFarming).connect(incentiveCreator).createEternalFarming( - { - pool: params.poolAddress, - rewardToken: params.rewardToken, - bonusRewardToken: params.bonusRewardToken, - nonce, - }, - { - reward: params.totalReward, - bonusReward: params.bonusReward, - rewardRate: params.rewardRate || 10, - bonusRewardRate: params.bonusRewardRate || 10, - minimalPositionWidth: params.minimalPositionWidth || 0, - }, - pluginAddres - ); - - // @ts-ignore - virtualPoolAddress = (await txResult.wait()).logs[3].args['virtualPool']; - return { - ..._.pick(params, ['poolAddress', 'totalReward', 'bonusReward', 'rewardToken', 'bonusRewardToken']), - nonce, - - virtualPool: new ethers.Contract(virtualPoolAddress, new Interface(abi.abi), this.actors.lpUser0()), - }; - }; - - /*** - * params.lp mints an NFT backed by a certain amount of `params.tokensToFarm`. - * - * Side-Effects: - * Funds `params.lp` with enough `params.tokensToFarm` if they do not have enough. - * Handles the ERC20 and ERC721 permits. - */ - mintDepositFarmFlow: HelperTypes.MintDepositFarm.Command = async (params) => { - // Make sure LP has enough balance - const bal0 = await params.tokensToFarm[0].balanceOf(params.lp.address); - if (bal0 < params.amountsToFarm[0]) - await params.tokensToFarm[0] - // .connect(tokensOwner) - .transfer(params.lp.address, params.amountsToFarm[0] * 2n); - - const bal1 = await params.tokensToFarm[1].balanceOf(params.lp.address); - if (bal1 < params.amountsToFarm[1]) - await params.tokensToFarm[1] - // .connect(tokensOwner) - .transfer(params.lp.address, params.amountsToFarm[1]); - - // Make sure LP has authorized NFT to withdraw - await params.tokensToFarm[0].connect(params.lp).approve(this.nft, params.amountsToFarm[0]); - await params.tokensToFarm[1].connect(params.lp).approve(this.nft, params.amountsToFarm[1]); - - // The LP mints their NFT - const tokenId = await mintPosition(this.nft.connect(params.lp), { - token0: await params.tokensToFarm[0].getAddress(), - token1: await params.tokensToFarm[1].getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: params.ticks[0], - tickUpper: params.ticks[1], - recipient: params.lp.address, - amount0Desired: params.amountsToFarm[0], - amount1Desired: params.amountsToFarm[1], - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - // The LP approves and farms their NFT - await this.nft.connect(params.lp).approveForFarming(tokenId, true, this.farmingCenter); - - await this.farmingCenter.connect(params.lp).enterFarming(await incentiveResultToFarmAdapter(params.createIncentiveResult), tokenId); - - const farmdAt = await blockTimestamp(); - - return { - tokenId, - farmdAt, - lp: params.lp, - }; - }; - - depositFlow: HelperTypes.Deposit.Command = async (params) => { - await this.nft.connect(params.lp).approveForFarming(params.tokenId, true, this.farmingCenter); - }; - - mintFlow: HelperTypes.Mint.Command = async (params) => { - const fee = params.fee || FeeAmount.MEDIUM; - const e20h = new ERC20Helper(); - - const amount0Desired = params.amounts ? params.amounts[0] : this.DEFAULT_LP_AMOUNT; - - await e20h.ensureBalancesAndApprovals(params.lp, params.tokens[0], amount0Desired, await this.nft.getAddress()); - - const amount1Desired = params.amounts ? params.amounts[1] : this.DEFAULT_LP_AMOUNT; - - await e20h.ensureBalancesAndApprovals(params.lp, params.tokens[1], amount1Desired, await this.nft.getAddress()); - - const tokenId = await mintPosition(this.nft.connect(params.lp), { - token0: await params.tokens[0].getAddress(), - token1: await params.tokens[1].getAddress(), - fee, - tickLower: params.tickLower || getMinTick(fee), - tickUpper: params.tickUpper || getMaxTick(fee), - recipient: params.lp.address, - amount0Desired, - amount1Desired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - return { tokenId, lp: params.lp }; - }; - - exitFarmingCollectBurnFlow: HelperTypes.exitFarmingCollectBurn.Command = async (params) => { - await this.farmingCenter.connect(params.lp).exitFarming(await incentiveResultToFarmAdapter(params.createIncentiveResult), params.tokenId, maxGas); - - const exitFarmingdAt = await blockTimestamp(); - - await this.eternalFarming.connect(params.lp).claimReward(params.createIncentiveResult.rewardToken, params.lp.address, 0); - - await this.eternalFarming.connect(params.lp).claimReward(params.createIncentiveResult.bonusRewardToken, params.lp.address, 0); - - const { liquidity } = await this.nft.connect(params.lp).positions(params.tokenId); - - await this.nft.connect(params.lp).decreaseLiquidity( - { - tokenId: params.tokenId, - liquidity, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }, - maxGas - ); - - const { tokensOwed0, tokensOwed1 } = await this.nft.connect(params.lp).positions(params.tokenId); - - await this.nft.connect(params.lp).collect( - { - tokenId: params.tokenId, - recipient: params.lp.address, - amount0Max: tokensOwed0, - amount1Max: tokensOwed1, - }, - maxGas - ); - - await this.nft.connect(params.lp).burn(params.tokenId, maxGas); - - const balance = await params.createIncentiveResult.rewardToken.connect(params.lp).balanceOf(params.lp.address); - const bonusBalance = await params.createIncentiveResult.bonusRewardToken.connect(params.lp).balanceOf(params.lp.address); - - return { - balance, - bonusBalance, - exitFarmingdAt, - }; - }; - - // endIncentiveFlow: HelperTypes.EndIncentive.Command = async (params) => { - // const incentiveCreator = this.actors.incentiveCreator() - // const { rewardToken } = params.createIncentiveResult - // - // const receipt = await ( - // await this.tokenomics.connect(incentiveCreator).endIncentive( - // _.assign({}, _.pick(params.createIncentiveResult, ['startTime', 'endTime']), { - // rewardToken: rewardToken.address, - // pool: params.createIncentiveResult.poolAddress - // }) - // ) - // ).wait() - // - // const transferFilter = rewardToken.filters.Transfer(this.tokenomics.address, incentiveCreator.address, null) - // const transferTopic = rewardToken.interface.getEventTopic('Transfer') - // const logItem = receipt.logs.find((log) => log.topics.includes(transferTopic)) - // const events = await rewardToken.queryFilter(transferFilter, logItem?.blockHash) - // let amountTransferred: BigNumber - // - // if (events.length === 1) { - // amountTransferred = events[0].args[2] - // } else { - // throw new Error('Could not find transfer event') - // } - // - // return { - // amountReturnedToCreator: amountTransferred, - // } - // } - - getIncentiveId: HelperTypes.GetIncentiveId.Command = async (params) => { - return this.testIncentiveId.compute({ - rewardToken: params.rewardToken, - bonusRewardToken: params.bonusRewardToken, - pool: params.poolAddress, - nonce: params.nonce, - }); - }; - - makeTickGoFlow: HelperTypes.MakeTickGo.Command = async (params) => { - // await tok0.transfer(trader0.address, BNe18(2).mul(params.numberOfTrades)) - // await tok0 - // .connect(trader0) - // .approve(router.address, BNe18(2).mul(params.numberOfTrades)) - - const MAKE_TICK_GO_UP = params.direction === 'up'; - const actor = params.trader || this.actors.traderUser0(); - - const isDone = (tick: number | undefined) => { - if (!params.desiredValue) { - return true; - } else if (!tick) { - return false; - } else if (MAKE_TICK_GO_UP) { - return tick > params.desiredValue; - } else { - return tick < params.desiredValue; - } - }; - - const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); - const erc20 = await ethers.getContractFactory('TestERC20'); - - const tok0 = erc20.attach(tok0Address) as any as TestERC20; - const tok1 = erc20.attach(tok1Address) as any as TestERC20; - const doTrade = async () => { - /** If we want to push price down, we need to increase tok0. - If we want to push price up, we need to increase tok1 */ - - const amountIn = BNe18(1); - - const erc20Helper = new ERC20Helper(); - await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); - - const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, ZERO_ADDRESS, tok0Address] : [tok0Address, ZERO_ADDRESS, tok1Address]); - - await this.router.connect(actor).exactInput( - { - recipient: actor.address, - deadline: MaxUint256, - path, - amountIn: amountIn / 10n, - amountOutMinimum: 0, - }, - maxGas - ); - let currTick = await getCurrentTick(this.pool.connect(actor)); - return currTick; - }; - - let currentTick = await doTrade(); - - while (!isDone(currentTick)) { - currentTick = await doTrade(); - } - - return { currentTick }; - }; - - moveTickTo: HelperTypes.MakeTickGo.Command = async (params) => { - Decimal.set({ toExpPos: 9_999_999, toExpNeg: -9_999_999, precision: 100 }); - - const actor = params.trader || this.actors.traderUser0(); - - const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); - const erc20 = await ethers.getContractFactory('TestERC20'); - - const tok0 = erc20.attach(tok0Address) as any as TestERC20; - const tok1 = erc20.attach(tok1Address) as any as TestERC20; - - let currentTick = await getCurrentTick(this.pool.connect(actor)); - - const targetTick = params.desiredValue; - - if (targetTick === undefined) throw new Error('No desired value'); - - if (targetTick == currentTick) return { currentTick }; - - const zto = targetTick < currentTick; - - const Q96 = new Decimal(2).pow(96); - - const priceAtTarget = - BigInt( - new Decimal(1.0001) - .pow(new Decimal(Number(targetTick)).div(2)) - .mul(Q96) - .round() - .toString() - ) + 100n; - - const erc20Helper = new ERC20Helper(); - const amountIn = (2n ** 128n - 1n) / 2n - 100n; - await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); - - await this.router.connect(actor).exactInputSingle( - { - recipient: actor.address, - deadline: MaxUint256, - tokenIn: zto ? tok0Address : tok1Address, - tokenOut: zto ? tok1Address : tok0Address, - deployer: ZERO_ADDRESS, - amountIn: 2n ** 128n - 1n, - amountOutMinimum: 0, - limitSqrtPrice: priceAtTarget, - }, - maxGas - ); - - currentTick = await getCurrentTick(this.pool.connect(actor)); - - return { - currentTick, - }; - }; - - makeTickGoFlowWithSmallSteps: HelperTypes.MakeTickGo.Command = async (params) => { - // await tok0.transfer(trader0.address, BNe18(2).mul(params.numberOfTrades)) - // await tok0 - // .connect(trader0) - // .approve(router.address, BNe18(2).mul(params.numberOfTrades)) - - const MAKE_TICK_GO_UP = params.direction === 'up'; - const actor = params.trader || this.actors.traderUser0(); - - const isDone = (tick: number | undefined) => { - if (!params.desiredValue) { - return true; - } else if (!tick) { - return false; - } else if (MAKE_TICK_GO_UP) { - return tick > params.desiredValue; - } else { - return tick < params.desiredValue; - } - }; - - const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); - const erc20 = await ethers.getContractFactory('TestERC20'); - - const tok0 = erc20.attach(tok0Address) as any as TestERC20; - const tok1 = erc20.attach(tok1Address) as any as TestERC20; - - const doTrade = async () => { - /** If we want to push price down, we need to increase tok0. - If we want to push price up, we need to increase tok1 */ - - const amountIn = 5n * 10n ** 16n; - - const erc20Helper = new ERC20Helper(); - await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); - - const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, tok0Address] : [tok0Address, tok1Address]); - - await this.router.connect(actor).exactInput( - { - recipient: actor.address, - deadline: MaxUint256, - path, - amountIn: amountIn / 10n, - amountOutMinimum: 0, - }, - maxGas - ); - let currTick = await getCurrentTick(this.pool.connect(actor)); - return currTick; - }; - - let currentTick = await doTrade(); - - while (!isDone(currentTick)) { - currentTick = await doTrade(); - } - - return { currentTick }; - }; - - makeSwapGasCHeckFlow: HelperTypes.MakeSwapGasCheck.Command = async (params) => { - // await tok0.transfer(trader0.address, BNe18(2).mul(params.numberOfTrades)) - // await tok0 - // .connect(trader0) - // .approve(router.address, BNe18(2).mul(params.numberOfTrades)) - - const MAKE_TICK_GO_UP = params.direction === 'up'; - const actor = params.trader || this.actors.traderUser0(); - - const isDone = (tick: number | undefined) => { - if (!params.desiredValue) { - return true; - } else if (!tick) { - return false; - } else if (MAKE_TICK_GO_UP) { - return tick > params.desiredValue; - } else { - return tick < params.desiredValue; - } - }; - - const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); - const erc20 = await ethers.getContractFactory('TestERC20'); - - const tok0 = erc20.attach(tok0Address) as any as TestERC20; - const tok1 = erc20.attach(tok1Address) as any as TestERC20; - - /** If we want to push price down, we need to increase tok0. - If we want to push price up, we need to increase tok1 */ - - const amountIn = params.amountIn ? BNe18(params.amountIn) : BNe18(1); - - const erc20Helper = new ERC20Helper(); - await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); - - const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, tok0Address] : [tok0Address, tok1Address]); - - return this.router.connect(actor).exactInput( - { - recipient: actor.address, - deadline: MaxUint256, - path, - amountIn: amountIn / 10n, - amountOutMinimum: 0, - }, - maxGas - ); - }; -} - -export class ERC20Helper { - ensureBalancesAndApprovals = async (actor: Wallet, tokens: TestERC20 | Array, balance: bigint, spender?: string) => { - for (let token of arrayWrap(tokens)) { - await this.ensureBalance(actor, token, balance); - if (spender) { - await this.ensureApproval(actor, token, balance, spender); - } - } - }; - - ensureBalance = async (actor: Wallet, token: TestERC20, balance: bigint) => { - const currentBalance = await token.balanceOf(actor.address); - if (currentBalance < balance) { - await token - // .connect(this.actors.tokensOwner()) - .transfer(actor.address, balance - currentBalance); - } - - // if (spender) { - // await this.ensureApproval(actor, token, balance, spender) - // } - - return await token.balanceOf(actor.address); - }; - - ensureApproval = async (actor: Wallet, token: TestERC20, balance: bigint, spender: string) => { - const currentAllowance = await token.allowance(actor.address, spender); - if (currentAllowance < balance) { - await token.connect(actor).approve(spender, balance); - } - }; -} - -type IncentiveAdapterFunc = (params: HelperTypes.CreateIncentive.Result) => Promise; - -export const incentiveResultToFarmAdapter: IncentiveAdapterFunc = async (params: any) => ({ - rewardToken: await params.rewardToken.getAddress(), - bonusRewardToken: await params.bonusRewardToken.getAddress(), - pool: params.poolAddress, - nonce: params.nonce, -}); +import { Wallet, MaxUint256, Interface } from 'ethers'; +import { blockTimestamp, BNe18, FeeAmount, getCurrentTick, maxGas, encodePath, arrayWrap, getMinTick, getMaxTick, ZERO_ADDRESS } from '../shared/index'; +import _ from 'lodash'; +import { TestERC20, INonfungiblePositionManager, AlgebraEternalFarming, IAlgebraPool, TestIncentiveId, FarmingCenter } from '../../typechain'; +import abi from '../../artifacts/contracts/farmings/EternalVirtualPool.sol/EternalVirtualPool.json'; +import { HelperTypes } from './types'; +import { ActorFixture } from '../shared/actors'; +import { mintPosition } from '../shared/fixtures'; +import { ISwapRouter } from '@cryptoalgebra/integral-periphery/typechain'; +import { ethers } from 'hardhat'; +import { ContractParams } from '../../types/contractParams'; +import { TestContext } from '../types'; +import Decimal from 'decimal.js'; + +/*** + * HelperCommands is a utility that abstracts away lower-tier ethereum details + * so that we can focus on core business logic. + * + * Each helper function should be a `HelperTypes.CommandFunction` + */ +export class HelperCommands { + actors: ActorFixture; + provider: any; + eternalFarming: AlgebraEternalFarming; + nft: INonfungiblePositionManager; + router: ISwapRouter; + pool: IAlgebraPool; + testIncentiveId: TestIncentiveId; + farmingCenter: FarmingCenter; + + DEFAULT_INCENTIVE_DURATION = 2_000; + DEFAULT_CLAIM_DURATION = 1_000; + DEFAULT_LP_AMOUNT = BNe18(10); + DEFAULT_FEE_AMOUNT = FeeAmount.MEDIUM; + + constructor({ + provider, + eternalFarming, + nft, + router, + pool, + actors, + testIncentiveId, + farmingCenter, + }: { + provider: any; + eternalFarming: AlgebraEternalFarming; + farmingCenter: FarmingCenter; + nft: INonfungiblePositionManager; + router: ISwapRouter; + pool: IAlgebraPool; + actors: ActorFixture; + testIncentiveId: TestIncentiveId; + }) { + this.actors = actors; + this.provider = provider; + this.eternalFarming = eternalFarming; + this.nft = nft; + this.router = router; + this.pool = pool; + this.testIncentiveId = testIncentiveId; + this.farmingCenter = farmingCenter; + } + + static fromTestContext = (context: TestContext, actors: ActorFixture, provider: any): HelperCommands => { + return new HelperCommands({ + actors, + provider, + nft: context.nft, + router: context.router, + eternalFarming: context.eternalFarming, + pool: context.poolObj, + testIncentiveId: context.testIncentiveId, + farmingCenter: context.farmingCenter, + }); + }; + + /*** + * Creates a staking incentive owned by `incentiveCreator` for `totalReward` of `rewardToken` + * + * Side-Effects: + * Transfers `rewardToken` to `incentiveCreator` if they do not have sufficient balance. + */ + createIncentiveFlow: HelperTypes.CreateIncentive.Command = async (params) => { + const { nonce } = params; + + const incentiveCreator = this.actors.incentiveCreator(); + + const bal = await params.rewardToken.balanceOf(incentiveCreator.address); + const bonusBal = await params.bonusRewardToken.balanceOf(incentiveCreator.address); + + if (bal < params.totalReward) { + await params.rewardToken.transfer(incentiveCreator.address, params.totalReward); + } + + if (bonusBal < params.bonusReward) { + await params.bonusRewardToken.transfer(incentiveCreator.address, params.bonusReward); + } + + let txResult; + let virtualPoolAddress; + + await params.rewardToken.connect(incentiveCreator).approve(this.eternalFarming, params.totalReward); + await params.bonusRewardToken.connect(incentiveCreator).approve(this.eternalFarming, params.bonusReward); + + let pluginAddres = params.plugin; + if (!pluginAddres) { + const pool = (await ethers.getContractAt('IAlgebraPool', params.poolAddress)) as any as IAlgebraPool; + pluginAddres = await pool.connect(incentiveCreator).plugin(); + } + + txResult = await (this.eternalFarming as AlgebraEternalFarming).connect(incentiveCreator).createEternalFarming( + { + pool: params.poolAddress, + rewardToken: params.rewardToken, + bonusRewardToken: params.bonusRewardToken, + nonce, + }, + { + reward: params.totalReward, + bonusReward: params.bonusReward, + rewardRate: params.rewardRate || 10, + bonusRewardRate: params.bonusRewardRate || 10, + minimalPositionWidth: params.minimalPositionWidth || 0, + }, + pluginAddres + ); + + // @ts-ignore + virtualPoolAddress = (await txResult.wait()).logs[3].args['virtualPool']; + return { + ..._.pick(params, ['poolAddress', 'totalReward', 'bonusReward', 'rewardToken', 'bonusRewardToken']), + nonce, + + virtualPool: new ethers.Contract(virtualPoolAddress, new Interface(abi.abi), this.actors.lpUser0()), + }; + }; + + /*** + * params.lp mints an NFT backed by a certain amount of `params.tokensToFarm`. + * + * Side-Effects: + * Funds `params.lp` with enough `params.tokensToFarm` if they do not have enough. + * Handles the ERC20 and ERC721 permits. + */ + mintDepositFarmFlow: HelperTypes.MintDepositFarm.Command = async (params) => { + // Make sure LP has enough balance + const bal0 = await params.tokensToFarm[0].balanceOf(params.lp.address); + if (bal0 < params.amountsToFarm[0]) + await params.tokensToFarm[0] + // .connect(tokensOwner) + .transfer(params.lp.address, params.amountsToFarm[0] * 2n); + + const bal1 = await params.tokensToFarm[1].balanceOf(params.lp.address); + if (bal1 < params.amountsToFarm[1]) + await params.tokensToFarm[1] + // .connect(tokensOwner) + .transfer(params.lp.address, params.amountsToFarm[1]); + + // Make sure LP has authorized NFT to withdraw + await params.tokensToFarm[0].connect(params.lp).approve(this.nft, params.amountsToFarm[0]); + await params.tokensToFarm[1].connect(params.lp).approve(this.nft, params.amountsToFarm[1]); + + // The LP mints their NFT + const tokenId = await mintPosition(this.nft.connect(params.lp), { + token0: await params.tokensToFarm[0].getAddress(), + token1: await params.tokensToFarm[1].getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: params.ticks[0], + tickUpper: params.ticks[1], + recipient: params.lp.address, + amount0Desired: params.amountsToFarm[0], + amount1Desired: params.amountsToFarm[1], + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + // The LP approves and farms their NFT + await this.nft.connect(params.lp).approveForFarming(tokenId, true, this.farmingCenter); + + await this.farmingCenter.connect(params.lp).enterFarming(await incentiveResultToFarmAdapter(params.createIncentiveResult), tokenId); + + const farmdAt = await blockTimestamp(); + + return { + tokenId, + farmdAt, + lp: params.lp, + }; + }; + + depositFlow: HelperTypes.Deposit.Command = async (params) => { + await this.nft.connect(params.lp).approveForFarming(params.tokenId, true, this.farmingCenter); + }; + + mintFlow: HelperTypes.Mint.Command = async (params) => { + const fee = params.fee || FeeAmount.MEDIUM; + const e20h = new ERC20Helper(); + + const amount0Desired = params.amounts ? params.amounts[0] : this.DEFAULT_LP_AMOUNT; + + await e20h.ensureBalancesAndApprovals(params.lp, params.tokens[0], amount0Desired, await this.nft.getAddress()); + + const amount1Desired = params.amounts ? params.amounts[1] : this.DEFAULT_LP_AMOUNT; + + await e20h.ensureBalancesAndApprovals(params.lp, params.tokens[1], amount1Desired, await this.nft.getAddress()); + + const tokenId = await mintPosition(this.nft.connect(params.lp), { + token0: await params.tokens[0].getAddress(), + token1: await params.tokens[1].getAddress(), + fee, + tickLower: params.tickLower || getMinTick(fee), + tickUpper: params.tickUpper || getMaxTick(fee), + recipient: params.lp.address, + amount0Desired, + amount1Desired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + return { tokenId, lp: params.lp }; + }; + + exitFarmingCollectBurnFlow: HelperTypes.exitFarmingCollectBurn.Command = async (params) => { + await this.farmingCenter.connect(params.lp).exitFarming(await incentiveResultToFarmAdapter(params.createIncentiveResult), params.tokenId, maxGas); + + const exitFarmingdAt = await blockTimestamp(); + + await this.eternalFarming.connect(params.lp).claimReward(params.createIncentiveResult.rewardToken, params.lp.address, 0); + + await this.eternalFarming.connect(params.lp).claimReward(params.createIncentiveResult.bonusRewardToken, params.lp.address, 0); + + const { liquidity } = await this.nft.connect(params.lp).positions(params.tokenId); + + await this.nft.connect(params.lp).decreaseLiquidity( + { + tokenId: params.tokenId, + liquidity, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }, + maxGas + ); + + const { tokensOwed0, tokensOwed1 } = await this.nft.connect(params.lp).positions(params.tokenId); + + await this.nft.connect(params.lp).collect( + { + tokenId: params.tokenId, + recipient: params.lp.address, + amount0Max: tokensOwed0, + amount1Max: tokensOwed1, + }, + maxGas + ); + + await this.nft.connect(params.lp).burn(params.tokenId, maxGas); + + const balance = await params.createIncentiveResult.rewardToken.connect(params.lp).balanceOf(params.lp.address); + const bonusBalance = await params.createIncentiveResult.bonusRewardToken.connect(params.lp).balanceOf(params.lp.address); + + return { + balance, + bonusBalance, + exitFarmingdAt, + }; + }; + + // endIncentiveFlow: HelperTypes.EndIncentive.Command = async (params) => { + // const incentiveCreator = this.actors.incentiveCreator() + // const { rewardToken } = params.createIncentiveResult + // + // const receipt = await ( + // await this.tokenomics.connect(incentiveCreator).endIncentive( + // _.assign({}, _.pick(params.createIncentiveResult, ['startTime', 'endTime']), { + // rewardToken: rewardToken.address, + // pool: params.createIncentiveResult.poolAddress + // }) + // ) + // ).wait() + // + // const transferFilter = rewardToken.filters.Transfer(this.tokenomics.address, incentiveCreator.address, null) + // const transferTopic = rewardToken.interface.getEventTopic('Transfer') + // const logItem = receipt.logs.find((log) => log.topics.includes(transferTopic)) + // const events = await rewardToken.queryFilter(transferFilter, logItem?.blockHash) + // let amountTransferred: BigNumber + // + // if (events.length === 1) { + // amountTransferred = events[0].args[2] + // } else { + // throw new Error('Could not find transfer event') + // } + // + // return { + // amountReturnedToCreator: amountTransferred, + // } + // } + + getIncentiveId: HelperTypes.GetIncentiveId.Command = async (params) => { + return this.testIncentiveId.compute({ + rewardToken: params.rewardToken, + bonusRewardToken: params.bonusRewardToken, + pool: params.poolAddress, + nonce: params.nonce, + }); + }; + + makeTickGoFlow: HelperTypes.MakeTickGo.Command = async (params) => { + // await tok0.transfer(trader0.address, BNe18(2).mul(params.numberOfTrades)) + // await tok0 + // .connect(trader0) + // .approve(router.address, BNe18(2).mul(params.numberOfTrades)) + + const MAKE_TICK_GO_UP = params.direction === 'up'; + const actor = params.trader || this.actors.traderUser0(); + + const isDone = (tick: number | undefined) => { + if (!params.desiredValue) { + return true; + } else if (!tick) { + return false; + } else if (MAKE_TICK_GO_UP) { + return tick > params.desiredValue; + } else { + return tick < params.desiredValue; + } + }; + + const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); + const erc20 = await ethers.getContractFactory('TestERC20'); + + const tok0 = erc20.attach(tok0Address) as any as TestERC20; + const tok1 = erc20.attach(tok1Address) as any as TestERC20; + const doTrade = async () => { + /** If we want to push price down, we need to increase tok0. + If we want to push price up, we need to increase tok1 */ + + const amountIn = BNe18(1); + + const erc20Helper = new ERC20Helper(); + await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); + + const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, ZERO_ADDRESS, tok0Address] : [tok0Address, ZERO_ADDRESS, tok1Address]); + + await this.router.connect(actor).exactInput( + { + recipient: actor.address, + deadline: MaxUint256, + path, + amountIn: amountIn / 10n, + amountOutMinimum: 0, + }, + maxGas + ); + let currTick = await getCurrentTick(this.pool.connect(actor)); + return currTick; + }; + + let currentTick = await doTrade(); + + while (!isDone(currentTick)) { + currentTick = await doTrade(); + } + + return { currentTick }; + }; + + moveTickTo: HelperTypes.MakeTickGo.Command = async (params) => { + Decimal.set({ toExpPos: 9_999_999, toExpNeg: -9_999_999, precision: 100 }); + + const actor = params.trader || this.actors.traderUser0(); + + const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); + const erc20 = await ethers.getContractFactory('TestERC20'); + + const tok0 = erc20.attach(tok0Address) as any as TestERC20; + const tok1 = erc20.attach(tok1Address) as any as TestERC20; + + let currentTick = await getCurrentTick(this.pool.connect(actor)); + + const targetTick = params.desiredValue; + + if (targetTick === undefined) throw new Error('No desired value'); + + if (targetTick == currentTick) return { currentTick }; + + const zto = targetTick < currentTick; + + const Q96 = new Decimal(2).pow(96); + + const priceAtTarget = + BigInt( + new Decimal(1.0001) + .pow(new Decimal(Number(targetTick)).div(2)) + .mul(Q96) + .round() + .toString() + ) + 100n; + + const erc20Helper = new ERC20Helper(); + const amountIn = (2n ** 128n - 1n) / 2n - 100n; + await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); + + await this.router.connect(actor).exactInputSingle( + { + recipient: actor.address, + deadline: MaxUint256, + tokenIn: zto ? tok0Address : tok1Address, + tokenOut: zto ? tok1Address : tok0Address, + deployer: ZERO_ADDRESS, + amountIn: 2n ** 128n - 1n, + amountOutMinimum: 0, + limitSqrtPrice: priceAtTarget, + }, + maxGas + ); + + currentTick = await getCurrentTick(this.pool.connect(actor)); + + return { + currentTick, + }; + }; + + makeTickGoFlowWithSmallSteps: HelperTypes.MakeTickGo.Command = async (params) => { + // await tok0.transfer(trader0.address, BNe18(2).mul(params.numberOfTrades)) + // await tok0 + // .connect(trader0) + // .approve(router.address, BNe18(2).mul(params.numberOfTrades)) + + const MAKE_TICK_GO_UP = params.direction === 'up'; + const actor = params.trader || this.actors.traderUser0(); + + const isDone = (tick: number | undefined) => { + if (!params.desiredValue) { + return true; + } else if (!tick) { + return false; + } else if (MAKE_TICK_GO_UP) { + return tick > params.desiredValue; + } else { + return tick < params.desiredValue; + } + }; + + const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); + const erc20 = await ethers.getContractFactory('TestERC20'); + + const tok0 = erc20.attach(tok0Address) as any as TestERC20; + const tok1 = erc20.attach(tok1Address) as any as TestERC20; + + const doTrade = async () => { + /** If we want to push price down, we need to increase tok0. + If we want to push price up, we need to increase tok1 */ + + const amountIn = 5n * 10n ** 16n; + + const erc20Helper = new ERC20Helper(); + await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); + + const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, tok0Address] : [tok0Address, tok1Address]); + + await this.router.connect(actor).exactInput( + { + recipient: actor.address, + deadline: MaxUint256, + path, + amountIn: amountIn / 10n, + amountOutMinimum: 0, + }, + maxGas + ); + let currTick = await getCurrentTick(this.pool.connect(actor)); + return currTick; + }; + + let currentTick = await doTrade(); + + while (!isDone(currentTick)) { + currentTick = await doTrade(); + } + + return { currentTick }; + }; + + makeSwapGasCHeckFlow: HelperTypes.MakeSwapGasCheck.Command = async (params) => { + // await tok0.transfer(trader0.address, BNe18(2).mul(params.numberOfTrades)) + // await tok0 + // .connect(trader0) + // .approve(router.address, BNe18(2).mul(params.numberOfTrades)) + + const MAKE_TICK_GO_UP = params.direction === 'up'; + const actor = params.trader || this.actors.traderUser0(); + + const isDone = (tick: number | undefined) => { + if (!params.desiredValue) { + return true; + } else if (!tick) { + return false; + } else if (MAKE_TICK_GO_UP) { + return tick > params.desiredValue; + } else { + return tick < params.desiredValue; + } + }; + + const [tok0Address, tok1Address] = await Promise.all([this.pool.connect(actor).token0(), this.pool.connect(actor).token1()]); + const erc20 = await ethers.getContractFactory('TestERC20'); + + const tok0 = erc20.attach(tok0Address) as any as TestERC20; + const tok1 = erc20.attach(tok1Address) as any as TestERC20; + + /** If we want to push price down, we need to increase tok0. + If we want to push price up, we need to increase tok1 */ + + const amountIn = params.amountIn ? BNe18(params.amountIn) : BNe18(1); + + const erc20Helper = new ERC20Helper(); + await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); + + const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, tok0Address] : [tok0Address, tok1Address]); + + return this.router.connect(actor).exactInput( + { + recipient: actor.address, + deadline: MaxUint256, + path, + amountIn: amountIn / 10n, + amountOutMinimum: 0, + }, + maxGas + ); + }; +} + +export class ERC20Helper { + ensureBalancesAndApprovals = async (actor: Wallet, tokens: TestERC20 | Array, balance: bigint, spender?: string) => { + for (let token of arrayWrap(tokens)) { + await this.ensureBalance(actor, token, balance); + if (spender) { + await this.ensureApproval(actor, token, balance, spender); + } + } + }; + + ensureBalance = async (actor: Wallet, token: TestERC20, balance: bigint) => { + const currentBalance = await token.balanceOf(actor.address); + if (currentBalance < balance) { + await token + // .connect(this.actors.tokensOwner()) + .transfer(actor.address, balance - currentBalance); + } + + // if (spender) { + // await this.ensureApproval(actor, token, balance, spender) + // } + + return await token.balanceOf(actor.address); + }; + + ensureApproval = async (actor: Wallet, token: TestERC20, balance: bigint, spender: string) => { + const currentAllowance = await token.allowance(actor.address, spender); + if (currentAllowance < balance) { + await token.connect(actor).approve(spender, balance); + } + }; +} + +type IncentiveAdapterFunc = (params: HelperTypes.CreateIncentive.Result) => Promise; + +export const incentiveResultToFarmAdapter: IncentiveAdapterFunc = async (params: any) => ({ + rewardToken: await params.rewardToken.getAddress(), + bonusRewardToken: await params.bonusRewardToken.getAddress(), + pool: params.poolAddress, + nonce: params.nonce, +}); diff --git a/src/farming/test/shared/externalFixtures.ts b/src/farming/test/shared/externalFixtures.ts index 1d7776408..9dd5c47a8 100644 --- a/src/farming/test/shared/externalFixtures.ts +++ b/src/farming/test/shared/externalFixtures.ts @@ -15,7 +15,7 @@ import { import { abi as PLUGIN_FACTORY_ABI, bytecode as PLUGIN_FACTORY_BYTECODE, -} from '@cryptoalgebra/integral-base-plugin/artifacts/contracts/BasePluginV1Factory.sol/BasePluginV1Factory.json'; +} from '@cryptoalgebra/default-plugin/artifacts/contracts/AlgebraDefaultPluginFactory.sol/AlgebraDefaultPluginFactory.json'; import { abi as WNATIVE_ABI, bytecode as WNATIVE_BYTECODE, diff --git a/src/farming/test/shared/fixtures.ts b/src/farming/test/shared/fixtures.ts index 3fdc27682..3bc29f712 100644 --- a/src/farming/test/shared/fixtures.ts +++ b/src/farming/test/shared/fixtures.ts @@ -1,313 +1,314 @@ -import { Signer, Wallet, getCreateAddress, MaxUint256 } from 'ethers'; -import { ethers } from 'hardhat'; - -import AlgebraPool from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraPool.sol/AlgebraPool.json'; -import AlgebraFactoryJson from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraFactory.sol/AlgebraFactory.json'; -import AlgebraPoolDeployerJson from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraPoolDeployer.sol/AlgebraPoolDeployer.json'; -import NFTDescriptorJson from '@cryptoalgebra/integral-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json'; -import NonfungiblePositionManagerJson from '@cryptoalgebra/integral-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'; -import NonfungibleTokenPositionDescriptor from '@cryptoalgebra/integral-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json'; -import SwapRouter from '@cryptoalgebra/integral-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'; -import WNativeToken from './external/WNativeToken.json'; -import { linkLibraries } from './linkLibraries'; -import { ISwapRouter, IWNativeToken, NFTDescriptor } from '@cryptoalgebra/integral-periphery/typechain'; -import { - abi as PLUGIN_FACTORY_ABI, - bytecode as PLUGIN_FACTORY_BYTECODE, -} from '@cryptoalgebra/integral-base-plugin/artifacts/contracts/BasePluginV1Factory.sol/BasePluginV1Factory.json'; - -import { - abi as PLUGIN_ABI, - bytecode as PLUGIN_BYTECODE, -} from '@cryptoalgebra/integral-base-plugin/artifacts/contracts/AlgebraBasePluginV1.sol/AlgebraBasePluginV1.json'; -import { - AlgebraEternalFarming, - TestERC20, - INonfungiblePositionManager, - IAlgebraFactory, - IAccessControl, - IAlgebraPoolDeployer, - IAlgebraPool, - TestIncentiveId, - FarmingCenter, -} from '../../typechain'; -import { FeeAmount, encodePriceSqrt, MAX_GAS_LIMIT, ZERO_ADDRESS } from '../shared'; -import { ActorFixture } from './actors'; -import { IBasePluginV1Factory, IAlgebraBasePluginV1 } from '@cryptoalgebra/integral-base-plugin/typechain'; - -type WNativeTokenFixture = { wnative: IWNativeToken }; - -type TestERC20WithAddress = TestERC20 & { address: string }; - -export const vaultAddress = '0x1d8b6fA722230153BE08C4Fa4Aa4B4c7cd01A95a'; - -export const wnativeFixture: () => Promise = async () => { - const wnativeFactory = await ethers.getContractFactory(WNativeToken.abi, WNativeToken.bytecode); - const wnative = (await wnativeFactory.deploy()) as any as IWNativeToken; - - return { wnative }; -}; - -const v3CoreFactoryFixture: () => Promise<[IAlgebraFactory, IAlgebraPoolDeployer, IBasePluginV1Factory, Signer]> = async () => { - const [deployer] = await ethers.getSigners(); - // precompute - const poolDeployerAddress = getCreateAddress({ - from: deployer.address, - nonce: (await ethers.provider.getTransactionCount(deployer.address)) + 1, - }); - - const v3FactoryFactory = await ethers.getContractFactory(AlgebraFactoryJson.abi, AlgebraFactoryJson.bytecode); - const _factory = (await v3FactoryFactory.deploy(poolDeployerAddress)) as any as IAlgebraFactory; - - const poolDeployerFactory = await ethers.getContractFactory(AlgebraPoolDeployerJson.abi, AlgebraPoolDeployerJson.bytecode); - const _deployer = (await poolDeployerFactory.deploy(_factory)) as any as IAlgebraPoolDeployer; - - const pluginContractFactory = await ethers.getContractFactory(PLUGIN_FACTORY_ABI, PLUGIN_FACTORY_BYTECODE); - const pluginFactory = (await pluginContractFactory.deploy(_factory)) as any as IBasePluginV1Factory; - - await _factory.setDefaultPluginFactory(pluginFactory); - - return [_factory, _deployer, pluginFactory, deployer]; -}; - -export const v3RouterFixture: () => Promise<{ - wnative: IWNativeToken; - factory: IAlgebraFactory; - deployer: IAlgebraPoolDeployer; - router: ISwapRouter; - pluginFactory: IBasePluginV1Factory; - ownerSigner: Signer; -}> = async () => { - const { wnative } = await wnativeFixture(); - const [factory, deployer, pluginFactory, ownerSigner] = await v3CoreFactoryFixture(); - const routerFactory = await ethers.getContractFactory(SwapRouter.abi, SwapRouter.bytecode); - const router = (await routerFactory.deploy(factory, wnative, deployer)) as any as ISwapRouter; - - return { factory, wnative, deployer, router, pluginFactory, ownerSigner }; -}; - -const nftDescriptorLibraryFixture: () => Promise = async () => { - const NFTDescriptorFactory = await ethers.getContractFactory(NFTDescriptorJson.abi, NFTDescriptorJson.bytecode); - return (await NFTDescriptorFactory.deploy()) as any as NFTDescriptor; -}; - -type AlgebraFactoryFixture = { - wnative: IWNativeToken; - factory: IAlgebraFactory; - deployer: IAlgebraPoolDeployer; - router: ISwapRouter; - nft: INonfungiblePositionManager; - tokens: [TestERC20, TestERC20, TestERC20, TestERC20]; - pluginFactory: IBasePluginV1Factory; - ownerSigner: Signer; -}; - -export const algebraFactoryFixture: () => Promise = async () => { - const { wnative, factory, deployer, router, pluginFactory, ownerSigner } = await v3RouterFixture(); - - const tokenFactory = await ethers.getContractFactory('TestERC20'); - const tokens = (await Promise.all([ - tokenFactory.deploy(MaxUint256 / 2n), // do not use maxu256 to avoid overflowing - tokenFactory.deploy(MaxUint256 / 2n), - tokenFactory.deploy(MaxUint256 / 2n), - tokenFactory.deploy(MaxUint256 / 2n), - ])) as [any, any, any, any] as [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; - - const nftDescriptorLibrary = await nftDescriptorLibraryFixture(); - const linkedBytecode = linkLibraries( - { - bytecode: NonfungibleTokenPositionDescriptor.bytecode, - linkReferences: { - 'NFTDescriptor.sol': { - NFTDescriptor: [ - { - length: 20, - start: NonfungibleTokenPositionDescriptor.linkReferences['contracts/libraries/NFTDescriptor.sol'].NFTDescriptor[0].start, - }, - ], - }, - }, - }, - { - NFTDescriptor: await nftDescriptorLibrary.getAddress(), - } - ); - - const NFTDescriptorFactory = await ethers.getContractFactory(NonfungibleTokenPositionDescriptor.abi, linkedBytecode); - - const positionDescriptor = await NFTDescriptorFactory.deploy(tokens[0], 'ETH', []); - - const nftFactory = await ethers.getContractFactory(NonfungiblePositionManagerJson.abi, NonfungiblePositionManagerJson.bytecode); - const nft = (await nftFactory.deploy(factory, wnative, positionDescriptor, deployer)) as any as INonfungiblePositionManager; - for (const token of tokens) { - token.address = await token.getAddress(); - } - tokens.sort((a, b) => (a.address.toLowerCase() < b.address.toLowerCase() ? -1 : 1)); - - return { - wnative, - factory, - deployer, - router, - tokens, - nft, - pluginFactory, - ownerSigner, - }; -}; - -export const mintPosition = async ( - nft: INonfungiblePositionManager, - mintParams: { - token0: string | TestERC20; - token1: string | TestERC20; - fee: FeeAmount; - tickLower: number; - tickUpper: number; - recipient: string; - amount0Desired: any; - amount1Desired: any; - amount0Min: number; - amount1Min: number; - deadline: number; - } -): Promise => { - let tokenId: bigint | undefined; - - if (typeof mintParams.token0 != 'string') mintParams.token0 = await mintParams.token0.getAddress(); - if (typeof mintParams.token1 != 'string') mintParams.token1 = await mintParams.token1.getAddress(); - - const receipt = await ( - await nft.mint( - { - token0: mintParams.token0, - token1: mintParams.token1, - deployer: ZERO_ADDRESS, - tickLower: mintParams.tickLower, - tickUpper: mintParams.tickUpper, - recipient: mintParams.recipient, - amount0Desired: mintParams.amount0Desired, - amount1Desired: mintParams.amount1Desired, - amount0Min: mintParams.amount0Min, - amount1Min: mintParams.amount1Min, - deadline: mintParams.deadline, - }, - { - gasLimit: MAX_GAS_LIMIT, - } - ) - ).wait(); - - const nftAddress = await nft.getAddress(); - if (!receipt) throw new Error('No receipt'); - - for (let i = 0; i < receipt.logs.length; i++) { - const log = receipt.logs[i]; - if (log.address === nftAddress) { - const event = nft.interface.parseLog(log as any as { topics: string[]; data: string }); - if (event?.name == 'Transfer') { - tokenId = event.args?.tokenId; - break; - } - } - } - - if (tokenId === undefined) { - throw 'could not find tokenId after mint'; - } else { - return tokenId.toString(); - } -}; - -export type AlgebraFixtureType = { - deployer: IAlgebraPoolDeployer; - fee: FeeAmount; - nft: INonfungiblePositionManager; - pool01: string; - pool12: string; - factory: IAlgebraFactory; - poolObj: IAlgebraPool; - pluginObj: IAlgebraBasePluginV1; - pluginFactory: IBasePluginV1Factory; - router: ISwapRouter; - eternalFarming: AlgebraEternalFarming; - farmingCenter: FarmingCenter; - testIncentiveId: TestIncentiveId; - tokens: [TestERC20, TestERC20, TestERC20, TestERC20]; - token0: TestERC20; - token1: TestERC20; - rewardToken: TestERC20; - bonusRewardToken: TestERC20; - ownerSigner: Signer; -}; -export const algebraFixture: () => Promise = async () => { - const { tokens, nft, factory, deployer, router, pluginFactory, ownerSigner } = await algebraFactoryFixture(); - const wallets = (await ethers.getSigners()) as any as Wallet[]; - const signer = new ActorFixture(wallets, ethers.provider).farmingDeployer(); - - const incentiveCreator = new ActorFixture(wallets, ethers.provider).incentiveCreator(); - - const eternalFarmingFactory = await ethers.getContractFactory('AlgebraEternalFarming', signer); - const eternalFarming = (await eternalFarmingFactory.deploy(deployer, nft)) as any as AlgebraEternalFarming; - - const farmingCenterFactory = await ethers.getContractFactory('FarmingCenter', signer); - - const farmingCenter = (await farmingCenterFactory.deploy(eternalFarming, nft)) as any as FarmingCenter; - - await nft.setFarmingCenter(farmingCenter); - - await eternalFarming.connect(ownerSigner).setFarmingCenterAddress(farmingCenter); - - const incentiveMakerRole = await eternalFarming.INCENTIVE_MAKER_ROLE(); - - await (factory as any as IAccessControl).grantRole(incentiveMakerRole, incentiveCreator.address); - - await pluginFactory.setFarmingAddress(farmingCenter); - - const testIncentiveIdFactory = await ethers.getContractFactory('TestIncentiveId', signer); - const testIncentiveId = (await testIncentiveIdFactory.deploy()) as any as TestIncentiveId; - - for (const token of tokens) { - await token.approve(nft, MaxUint256); - } - - const fee = FeeAmount.MEDIUM; - - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1),'0x'); - - await nft.createAndInitializePoolIfNecessary(tokens[1], tokens[2], ZERO_ADDRESS, encodePriceSqrt(1, 1),'0x'); - - const pool01 = await factory.poolByPair(tokens[0], tokens[1]); - - const pool12 = await factory.poolByPair(tokens[1], tokens[2]); - - const poolObj = poolFactory.attach(pool01) as any as IAlgebraPool; - - const pluginContractFactory = new ethers.ContractFactory(PLUGIN_ABI, PLUGIN_BYTECODE, signer); - - const pluginObj = pluginContractFactory.attach(await poolObj.connect(signer).plugin()) as any as IAlgebraBasePluginV1; - - return { - nft, - router, - tokens, - eternalFarming, - farmingCenter, - testIncentiveId, - deployer, - factory, - pool01, - pool12, - fee, - poolObj, - pluginObj, - pluginFactory, - token0: tokens[0], - token1: tokens[1], - rewardToken: tokens[2], - bonusRewardToken: tokens[1], - ownerSigner, - }; -}; - -export const poolFactory = new ethers.ContractFactory(AlgebraPool.abi, AlgebraPool.bytecode); +import { Signer, Wallet, getCreateAddress, MaxUint256 } from 'ethers'; +import { ethers } from 'hardhat'; + +import AlgebraPool from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraPool.sol/AlgebraPool.json'; +import AlgebraFactoryJson from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraFactory.sol/AlgebraFactory.json'; +import AlgebraPoolDeployerJson from '@cryptoalgebra/integral-core/artifacts/contracts/AlgebraPoolDeployer.sol/AlgebraPoolDeployer.json'; +import NFTDescriptorJson from '@cryptoalgebra/integral-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json'; +import NonfungiblePositionManagerJson from '@cryptoalgebra/integral-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'; +import NonfungibleTokenPositionDescriptor from '@cryptoalgebra/integral-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json'; +import SwapRouter from '@cryptoalgebra/integral-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'; +import WNativeToken from './external/WNativeToken.json'; +import { linkLibraries } from './linkLibraries'; +import { ISwapRouter, IWNativeToken, NFTDescriptor } from '@cryptoalgebra/integral-periphery/typechain'; +import { + abi as PLUGIN_FACTORY_ABI, + bytecode as PLUGIN_FACTORY_BYTECODE, +} from '@cryptoalgebra/default-plugin/artifacts/contracts/AlgebraDefaultPluginFactory.sol/AlgebraDefaultPluginFactory.json'; + +import { + abi as PLUGIN_ABI, + bytecode as PLUGIN_BYTECODE, +} from '@cryptoalgebra/default-plugin/artifacts/contracts/AlgebraDefaultPlugin.sol/AlgebraDefaultPlugin.json'; +import { + AlgebraEternalFarming, + TestERC20, + INonfungiblePositionManager, + IAlgebraFactory, + IAccessControl, + IAlgebraPoolDeployer, + IAlgebraPool, + TestIncentiveId, + FarmingCenter, + IAlgebraDefaultPluginFactory, + IFarmingPlugin +} from '../../typechain'; +import { FeeAmount, encodePriceSqrt, MAX_GAS_LIMIT, ZERO_ADDRESS } from '../shared'; +import { ActorFixture } from './actors'; + +type WNativeTokenFixture = { wnative: IWNativeToken }; + +type TestERC20WithAddress = TestERC20 & { address: string }; + +export const vaultAddress = '0x1d8b6fA722230153BE08C4Fa4Aa4B4c7cd01A95a'; + +export const wnativeFixture: () => Promise = async () => { + const wnativeFactory = await ethers.getContractFactory(WNativeToken.abi, WNativeToken.bytecode); + const wnative = (await wnativeFactory.deploy()) as any as IWNativeToken; + + return { wnative }; +}; + +const v3CoreFactoryFixture: () => Promise<[IAlgebraFactory, IAlgebraPoolDeployer, IAlgebraDefaultPluginFactory, Signer]> = async () => { + const [deployer] = await ethers.getSigners(); + // precompute + const poolDeployerAddress = getCreateAddress({ + from: deployer.address, + nonce: (await ethers.provider.getTransactionCount(deployer.address)) + 1, + }); + + const v3FactoryFactory = await ethers.getContractFactory(AlgebraFactoryJson.abi, AlgebraFactoryJson.bytecode); + const _factory = (await v3FactoryFactory.deploy(poolDeployerAddress)) as any as IAlgebraFactory; + + const poolDeployerFactory = await ethers.getContractFactory(AlgebraPoolDeployerJson.abi, AlgebraPoolDeployerJson.bytecode); + const _deployer = (await poolDeployerFactory.deploy(_factory)) as any as IAlgebraPoolDeployer; + + const pluginContractFactory = await ethers.getContractFactory(PLUGIN_FACTORY_ABI, PLUGIN_FACTORY_BYTECODE); + const pluginFactory = (await pluginContractFactory.deploy(_factory)) as any as IAlgebraDefaultPluginFactory; + + await _factory.setDefaultPluginFactory(pluginFactory); + + return [_factory, _deployer, pluginFactory, deployer]; +}; + +export const v3RouterFixture: () => Promise<{ + wnative: IWNativeToken; + factory: IAlgebraFactory; + deployer: IAlgebraPoolDeployer; + router: ISwapRouter; + pluginFactory: IAlgebraDefaultPluginFactory; + ownerSigner: Signer; +}> = async () => { + const { wnative } = await wnativeFixture(); + const [factory, deployer, pluginFactory, ownerSigner] = await v3CoreFactoryFixture(); + const routerFactory = await ethers.getContractFactory(SwapRouter.abi, SwapRouter.bytecode); + const router = (await routerFactory.deploy(factory, wnative, deployer)) as any as ISwapRouter; + + return { factory, wnative, deployer, router, pluginFactory, ownerSigner }; +}; + +const nftDescriptorLibraryFixture: () => Promise = async () => { + const NFTDescriptorFactory = await ethers.getContractFactory(NFTDescriptorJson.abi, NFTDescriptorJson.bytecode); + return (await NFTDescriptorFactory.deploy()) as any as NFTDescriptor; +}; + +type AlgebraFactoryFixture = { + wnative: IWNativeToken; + factory: IAlgebraFactory; + deployer: IAlgebraPoolDeployer; + router: ISwapRouter; + nft: INonfungiblePositionManager; + tokens: [TestERC20, TestERC20, TestERC20, TestERC20]; + pluginFactory: IAlgebraDefaultPluginFactory; + ownerSigner: Signer; +}; + +export const algebraFactoryFixture: () => Promise = async () => { + const { wnative, factory, deployer, router, pluginFactory, ownerSigner } = await v3RouterFixture(); + + const tokenFactory = await ethers.getContractFactory('TestERC20'); + const tokens = (await Promise.all([ + tokenFactory.deploy(MaxUint256 / 2n), // do not use maxu256 to avoid overflowing + tokenFactory.deploy(MaxUint256 / 2n), + tokenFactory.deploy(MaxUint256 / 2n), + tokenFactory.deploy(MaxUint256 / 2n), + ])) as [any, any, any, any] as [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + + const nftDescriptorLibrary = await nftDescriptorLibraryFixture(); + const linkedBytecode = linkLibraries( + { + bytecode: NonfungibleTokenPositionDescriptor.bytecode, + linkReferences: { + 'NFTDescriptor.sol': { + NFTDescriptor: [ + { + length: 20, + start: NonfungibleTokenPositionDescriptor.linkReferences['contracts/libraries/NFTDescriptor.sol'].NFTDescriptor[0].start, + }, + ], + }, + }, + }, + { + NFTDescriptor: await nftDescriptorLibrary.getAddress(), + } + ); + + const NFTDescriptorFactory = await ethers.getContractFactory(NonfungibleTokenPositionDescriptor.abi, linkedBytecode); + + const positionDescriptor = await NFTDescriptorFactory.deploy(tokens[0], 'ETH', []); + + const nftFactory = await ethers.getContractFactory(NonfungiblePositionManagerJson.abi, NonfungiblePositionManagerJson.bytecode); + const nft = (await nftFactory.deploy(factory, wnative, positionDescriptor, deployer)) as any as INonfungiblePositionManager; + for (const token of tokens) { + token.address = await token.getAddress(); + } + tokens.sort((a, b) => (a.address.toLowerCase() < b.address.toLowerCase() ? -1 : 1)); + + return { + wnative, + factory, + deployer, + router, + tokens, + nft, + pluginFactory, + ownerSigner, + }; +}; + +export const mintPosition = async ( + nft: INonfungiblePositionManager, + mintParams: { + token0: string | TestERC20; + token1: string | TestERC20; + fee: FeeAmount; + tickLower: number; + tickUpper: number; + recipient: string; + amount0Desired: any; + amount1Desired: any; + amount0Min: number; + amount1Min: number; + deadline: number; + } +): Promise => { + let tokenId: bigint | undefined; + + if (typeof mintParams.token0 != 'string') mintParams.token0 = await mintParams.token0.getAddress(); + if (typeof mintParams.token1 != 'string') mintParams.token1 = await mintParams.token1.getAddress(); + + const receipt = await ( + await nft.mint( + { + token0: mintParams.token0, + token1: mintParams.token1, + deployer: ZERO_ADDRESS, + tickLower: mintParams.tickLower, + tickUpper: mintParams.tickUpper, + recipient: mintParams.recipient, + amount0Desired: mintParams.amount0Desired, + amount1Desired: mintParams.amount1Desired, + amount0Min: mintParams.amount0Min, + amount1Min: mintParams.amount1Min, + deadline: mintParams.deadline, + }, + { + gasLimit: MAX_GAS_LIMIT, + } + ) + ).wait(); + + const nftAddress = await nft.getAddress(); + if (!receipt) throw new Error('No receipt'); + + for (let i = 0; i < receipt.logs.length; i++) { + const log = receipt.logs[i]; + if (log.address === nftAddress) { + const event = nft.interface.parseLog(log as any as { topics: string[]; data: string }); + if (event?.name == 'Transfer') { + tokenId = event.args?.tokenId; + break; + } + } + } + + if (tokenId === undefined) { + throw 'could not find tokenId after mint'; + } else { + return tokenId.toString(); + } +}; + +export type AlgebraFixtureType = { + deployer: IAlgebraPoolDeployer; + fee: FeeAmount; + nft: INonfungiblePositionManager; + pool01: string; + pool12: string; + factory: IAlgebraFactory; + poolObj: IAlgebraPool; + pluginObj: IFarmingPlugin; + pluginFactory: IAlgebraDefaultPluginFactory; + router: ISwapRouter; + eternalFarming: AlgebraEternalFarming; + farmingCenter: FarmingCenter; + testIncentiveId: TestIncentiveId; + tokens: [TestERC20, TestERC20, TestERC20, TestERC20]; + token0: TestERC20; + token1: TestERC20; + rewardToken: TestERC20; + bonusRewardToken: TestERC20; + ownerSigner: Signer; +}; +export const algebraFixture: () => Promise = async () => { + const { tokens, nft, factory, deployer, router, pluginFactory, ownerSigner } = await algebraFactoryFixture(); + const wallets = (await ethers.getSigners()) as any as Wallet[]; + const signer = new ActorFixture(wallets, ethers.provider).farmingDeployer(); + + const incentiveCreator = new ActorFixture(wallets, ethers.provider).incentiveCreator(); + + const eternalFarmingFactory = await ethers.getContractFactory('AlgebraEternalFarming', signer); + const eternalFarming = (await eternalFarmingFactory.deploy(deployer, nft)) as any as AlgebraEternalFarming; + + const farmingCenterFactory = await ethers.getContractFactory('FarmingCenter', signer); + + const farmingCenter = (await farmingCenterFactory.deploy(eternalFarming, nft)) as any as FarmingCenter; + + await nft.setFarmingCenter(farmingCenter); + + await eternalFarming.connect(ownerSigner).setFarmingCenterAddress(farmingCenter); + + const incentiveMakerRole = await eternalFarming.INCENTIVE_MAKER_ROLE(); + + await (factory as any as IAccessControl).grantRole(incentiveMakerRole, incentiveCreator.address); + + await pluginFactory.setFarmingAddress(farmingCenter); + + const testIncentiveIdFactory = await ethers.getContractFactory('TestIncentiveId', signer); + const testIncentiveId = (await testIncentiveIdFactory.deploy()) as any as TestIncentiveId; + + for (const token of tokens) { + await token.approve(nft, MaxUint256); + } + + const fee = FeeAmount.MEDIUM; + + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1),'0x'); + + await nft.createAndInitializePoolIfNecessary(tokens[1], tokens[2], ZERO_ADDRESS, encodePriceSqrt(1, 1),'0x'); + + const pool01 = await factory.poolByPair(tokens[0], tokens[1]); + + const pool12 = await factory.poolByPair(tokens[1], tokens[2]); + + const poolObj = poolFactory.attach(pool01) as any as IAlgebraPool; + + const pluginContractFactory = new ethers.ContractFactory(PLUGIN_ABI, PLUGIN_BYTECODE, signer); + + const pluginObj = pluginContractFactory.attach(await poolObj.connect(signer).plugin()) as any as IFarmingPlugin; + + return { + nft, + router, + tokens, + eternalFarming, + farmingCenter, + testIncentiveId, + deployer, + factory, + pool01, + pool12, + fee, + poolObj, + pluginObj, + pluginFactory, + token0: tokens[0], + token1: tokens[1], + rewardToken: tokens[2], + bonusRewardToken: tokens[1], + ownerSigner, + }; +}; + +export const poolFactory = new ethers.ContractFactory(AlgebraPool.abi, AlgebraPool.bytecode); diff --git a/src/farming/test/unit/EternalFarms.spec.ts b/src/farming/test/unit/EternalFarms.spec.ts index 51ac99c6b..3528175fe 100644 --- a/src/farming/test/unit/EternalFarms.spec.ts +++ b/src/farming/test/unit/EternalFarms.spec.ts @@ -1,2428 +1,2428 @@ -import { ethers } from 'hardhat'; -import { Contract, Wallet, MaxUint256 } from 'ethers'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { TestERC20, AlgebraEternalFarming, EternalVirtualPool, TestERC20Reentrant } from '../../typechain'; -import { mintPosition, AlgebraFixtureType, algebraFixture } from '../shared/fixtures'; -import { - expect, - getMaxTick, - getMinTick, - FeeAmount, - TICK_SPACINGS, - blockTimestamp, - BN, - BNe18, - snapshotGasCost, - ActorFixture, - makeTimestamps, - ZERO_ADDRESS, - encodePriceSqrt, -} from '../shared'; -import { provider } from '../shared/provider'; -import { HelperCommands, ERC20Helper, incentiveResultToFarmAdapter } from '../helpers'; -import { ContractParams } from '../../types/contractParams'; -import { createTimeMachine } from '../shared/time'; -import { HelperTypes } from '../helpers/types'; - -describe('unit/EternalFarms', () => { - let actors: ActorFixture; - let lpUser0: Wallet; - let incentiveCreator: Wallet; - const amountDesired = BNe18(10); - const totalReward = 10000n; - const erc20Helper = new ERC20Helper(); - const Time = createTimeMachine(); - let nonce = BN(0); - let bonusReward = 200n; - let helpers: HelperCommands; - let context: AlgebraFixtureType; - let timestamps: ContractParams.Timestamps; - let tokenId: string; - - const detachIncentiveIndirectly = async (localNonce: any) => { - await context.pluginFactory.setFarmingAddress(actors.algebraRootUser().address); - - const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - const _tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: -120, - tickUpper: 120, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - await helpers.mintFlow({ - lp: lpUser0, - tokens: [context.token0, context.token1], - }); - - await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - _tokenId - ); - - await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(ZERO_ADDRESS); - - const tick = (await context.poolObj.connect(actors.algebraRootUser()).globalState()).tick; - - await helpers.moveTickTo({ direction: 'down', desiredValue: Number(tick) - 130, trader: actors.farmingDeployer() }); - - await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(incentiveAddress); - - await helpers.moveTickTo({ direction: 'up', desiredValue: Number(tick) - 125, trader: actors.farmingDeployer() }); - - await context.pluginFactory.setFarmingAddress(context.farmingCenter); - - const virtualPoolFactory = await ethers.getContractFactory('EternalVirtualPool'); - const deactivated = await (virtualPoolFactory.attach(incentiveAddress) as any as EternalVirtualPool).deactivated(); - expect(deactivated).to.be.true; - }; - - before(async () => { - const wallets = (await ethers.getSigners()) as any as Wallet[]; - actors = new ActorFixture(wallets, provider); - lpUser0 = actors.lpUser0(); - incentiveCreator = actors.incentiveCreator(); - }); - - beforeEach('load fixture', async () => { - context = await loadFixture(algebraFixture); - helpers = HelperCommands.fromTestContext(context, actors, provider); - }); - - describe('#onlyFarmingCenter ', () => { - const dummyKey = { - rewardToken: ZERO_ADDRESS, - bonusRewardToken: ZERO_ADDRESS, - pool: ZERO_ADDRESS, - nonce: 0, - }; - - it('reverts if not farmingCenter', async () => { - expect(context.eternalFarming.connect(actors.farmingDeployer()).claimRewardFrom(context.rewardToken, lpUser0.address, lpUser0.address, 100)).to - .be.revertedWithoutReason; - - expect(context.eternalFarming.connect(actors.farmingDeployer()).enterFarming(dummyKey, 1)).to.be.revertedWithoutReason; - - expect(context.eternalFarming.connect(actors.farmingDeployer()).exitFarming(dummyKey, 1, ZERO_ADDRESS)).to.be.revertedWithoutReason; - - expect(context.eternalFarming.connect(actors.farmingDeployer()).collectRewards(dummyKey, 1, ZERO_ADDRESS)).to.be.revertedWithoutReason; - }); - }); - - describe('#onlyIncentiveMaker', () => { - const dummyKey = { - rewardToken: ZERO_ADDRESS, - bonusRewardToken: ZERO_ADDRESS, - pool: ZERO_ADDRESS, - nonce: 0, - }; - - it('reverts if not incentiveMaker', async () => { - expect( - context.eternalFarming.connect(actors.farmingDeployer()).createEternalFarming( - dummyKey, - { - reward: 100, - bonusReward: 100, - rewardRate: 100, - bonusRewardRate: 100, - minimalPositionWidth: 100, - }, - await context.poolObj.connect(incentiveCreator).plugin() - ) - ).to.be.revertedWithoutReason; - - expect(context.eternalFarming.connect(actors.farmingDeployer()).deactivateIncentive(dummyKey)).to.be.revertedWithoutReason; - - expect(context.eternalFarming.connect(actors.farmingDeployer()).setRates(dummyKey, 10, 10)).to.be.revertedWithoutReason; - }); - }); - - describe('#setFarmingCenterAddress', async () => { - beforeEach(async () => { - context = await loadFixture(algebraFixture); - }); - - it('only administrator', async () => { - expect(context.eternalFarming.connect(actors.lpUser0()).setFarmingCenterAddress(ZERO_ADDRESS)).to.be.revertedWithoutReason; - }); - - it('cannot set the same farming center', async () => { - expect(context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(context.farmingCenter)).to.be.revertedWithoutReason; - }); - - it('can set new farming center', async () => { - await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(ZERO_ADDRESS); - expect(await context.eternalFarming.farmingCenter()).to.be.eq(ZERO_ADDRESS); - }); - }); - - describe('#EmergencyWithdraw', async () => { - beforeEach(async () => { - context = await loadFixture(algebraFixture); - }); - - it('only administrator', async () => { - expect(context.eternalFarming.connect(actors.lpUser0()).setEmergencyWithdrawStatus(true)).to.be.revertedWithoutReason; - }); - - it('cannot set the current value', async () => { - expect(context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(false)).to.be.revertedWithoutReason; - }); - - it('can set new value', async () => { - await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); - expect(await context.eternalFarming.isEmergencyWithdrawActivated()).to.be.eq(true); - }); - - it('can not enter in farming if emergency', async () => { - const helpers = HelperCommands.fromTestContext(context, actors, provider); - - const localNonce = await context.eternalFarming.numOfIncentives(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - const incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - - await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await expect( - context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ) - ).to.be.revertedWithCustomError(context.eternalFarming, 'emergencyActivated'); - }); - - it('can exit from farming if emergency', async () => { - const helpers = HelperCommands.fromTestContext(context, actors, provider); - - const localNonce = await context.eternalFarming.numOfIncentives(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - const incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - - await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); - - await context.farmingCenter.connect(lpUser0).exitFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - }); - }); - - describe('#isIncentiveDeactivated', async () => { - let localNonce = 0n; - let incentiveArgs; - let incentiveId: string; - - beforeEach(async () => { - context = await loadFixture(algebraFixture); - helpers = HelperCommands.fromTestContext(context, actors, provider); - - localNonce = await context.eternalFarming.numOfIncentives(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - }); - - it('false if incentive active', async () => { - expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.false; - }); - - it('true if incentive deactivated', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }); - expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.true; - }); - - it('true if incentive deactivated indirectly', async () => { - await detachIncentiveIndirectly(localNonce); - - expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.true; - }); - }); - - describe('#incentiveKeys', async () => { - let localNonce = 0n; - let incentiveArgs; - let incentiveId: string; - let incentiveKey; - - beforeEach(async () => { - context = await loadFixture(algebraFixture); - helpers = HelperCommands.fromTestContext(context, actors, provider); - - localNonce = await context.eternalFarming.numOfIncentives(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - incentiveKey = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: await context.poolObj.getAddress(), - nonce: localNonce, - } - - incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - }); - - it('returns incentive key after farming creation', async () => { - let {rewardToken, bonusRewardToken, pool, nonce} = await context.eternalFarming.incentiveKeys(await context.poolObj.getAddress()) - expect(rewardToken, bonusRewardToken, pool, nonce).to.be.eq(incentiveKey.rewardToken, incentiveKey.bonusRewardToken, incentiveKey.pool, incentiveKey.nonce); - }); - - it('returns zero key after farming deactivation', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }); - - let {rewardToken, bonusRewardToken, pool, nonce} = await context.eternalFarming.incentiveKeys(await context.poolObj.getAddress()) - expect(rewardToken, bonusRewardToken, pool, nonce).to.be.eq(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, 0); - }); - - it('true if incentive deactivated indirectly', async () => { - await detachIncentiveIndirectly(localNonce); - - expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.true; - }); - }); - - describe('#createEternalFarming', () => { - let localNonce = 0n; - - beforeEach(async () => { - context = await loadFixture(algebraFixture); - helpers = HelperCommands.fromTestContext(context, actors, provider); - - /** We will be doing a lot of time-testing here, so leave some room between - and when the incentive starts */ - localNonce = await context.eternalFarming.numOfIncentives(); - }); - - it('cannot create farming without rewards', async () => { - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward: 0n, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'zeroRewardAmount' - ); - }); - - it('cannot create farming if plugin is not connected', async () => { - await context.poolObj.connect(actors.wallets[0]).setPlugin(ZERO_ADDRESS); - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward: 10n, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'pluginNotConnected' - ); - }); - - it('cannot create farming if incorrect plugin is connected', async () => { - await context.poolObj.connect(actors.wallets[0]).setPlugin(actors.wallets[1].address); - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward: 10n, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - plugin: actors.wallets[0].address, - }; - - await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'pluginNotConnected' - ); - }); - - it('cannot set too wide minimal position width', async () => { - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - minimalPositionWidth: 2 ** 24 - 1, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'minimalPositionWidthTooWide' - ); - }); - - it('cannot create second eternal farming for one pool', async () => { - const incentiveArgsBase = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - await helpers.createIncentiveFlow(incentiveArgsBase); - - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce + 1n, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'anotherFarmingIsActive' - ); - }); - }); - - describe('#enterFarming', () => { - let incentiveId: string; - let incentiveArgs: HelperTypes.CreateIncentive.Args; - let subject: (L2TokenId: string, _actor: Wallet) => Promise; - let localNonce = 0n; - - beforeEach(async () => { - context = await loadFixture(algebraFixture); - helpers = HelperCommands.fromTestContext(context, actors, provider); - - localNonce = await context.eternalFarming.numOfIncentives(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - subject = (L2TokenId: string, _actor: Wallet) => - context.farmingCenter.connect(_actor).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - L2TokenId - ); - }); - - describe('increaseLiqudity', () => { - it('liquidity updated correct', async () => { - await subject(tokenId, lpUser0); - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - let farmBefore = await context.eternalFarming.farms(tokenId, incentiveId); - await context.nft.connect(lpUser0).increaseLiquidity({ - tokenId: tokenId, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - let farmAfter = await context.eternalFarming.farms(tokenId, incentiveId); - expect(farmAfter.liquidity - farmBefore.liquidity).to.eq(amountDesired); - }); - }); - - describe('works and', () => { - it('emits the farm event', async () => { - const { liquidity } = await context.nft.positions(tokenId); - await expect(subject(tokenId, lpUser0)).to.emit(context.eternalFarming, 'FarmEntered').withArgs(tokenId, incentiveId, liquidity); - }); - - it('sets the farm struct properly', async () => { - const liquidity = (await context.nft.positions(tokenId)).liquidity; - - const farmBefore = await context.eternalFarming.farms(tokenId, incentiveId); - await subject(tokenId, lpUser0); - const farmAfter = await context.eternalFarming.farms(tokenId, incentiveId); - - expect(farmBefore.liquidity).to.eq(0); - expect(farmAfter.liquidity).to.eq(liquidity); - }); - - it('has gas cost [ @skip-on-coverage ]', async () => await snapshotGasCost(subject(tokenId, lpUser0))); - }); - - describe('fails when', () => { - it('deposit is already farmd in the incentive', async () => { - await subject(tokenId, lpUser0); - await expect(subject(tokenId, lpUser0)).to.be.revertedWith('Token already farmed'); - }); - - it('trying to forcefully enter twice', async () => { - await subject(tokenId, lpUser0); - await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(lpUser0); - - await expect( - context.eternalFarming.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ) - ).to.be.revertedWithCustomError(context.eternalFarming, 'tokenAlreadyFarmed'); - }); - - it('farming deactivated', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }); - await expect(subject(tokenId, lpUser0)).to.be.revertedWithCustomError(context.eternalFarming, 'incentiveStopped'); - }); - - it('farming indirectly deactivated', async () => { - await detachIncentiveIndirectly(localNonce); - - await expect(subject(tokenId, lpUser0)).to.be.revertedWithCustomError(context.eternalFarming, 'incentiveStopped'); - }); - - it('you are not the owner of the deposit', async () => { - // lpUser2 calls, we're using lpUser0 elsewhere. - await expect(subject(tokenId, actors.lpUser2())).to.be.revertedWith('Not approved for token'); - }); - - it('has 0 liquidity in the position', async () => { - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - const tokenId2 = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await context.nft.connect(lpUser0).approveForFarming(tokenId2, true, context.farmingCenter); - - await context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenId2, - liquidity: (await context.nft.positions(tokenId2)).liquidity, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1_000, - }); - await expect(subject(tokenId2, lpUser0)).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'zeroLiquidity'); - }); - - it('token id is for a different pool than the incentive', async () => { - const incentive2 = await helpers.createIncentiveFlow({ - ...incentiveArgs, - poolAddress: context.pool12, - }); - const { tokenId: otherTokenId } = await helpers.mintFlow({ - lp: lpUser0, - tokens: [context.token1, context.rewardToken], - }); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId: otherTokenId, - }); - - await expect( - context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - otherTokenId - ) - ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'invalidPool'); - }); - - it('incentive key does not exist', async () => { - const _nonce = BN(999); - await expect( - context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: _nonce, - }, - tokenId - ) - ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'incentiveNotExist'); - }); - }); - }); - - describe('#getRewardInfo', () => { - let incentiveId: string; - let farmIncentiveKey: ContractParams.IncentiveKey; - - beforeEach('set up incentive and farm', async () => { - timestamps = makeTimestamps((await blockTimestamp()) + 1_000); - - const mintResult = await helpers.mintFlow({ - lp: lpUser0, - tokens: [context.token0, context.token1], - }); - tokenId = mintResult.tokenId; - - farmIncentiveKey = { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: 0, - }; - - incentiveId = await helpers.getIncentiveId( - await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: 0n, - rewardRate: 10n, - bonusRewardRate: 50n, - }) - ); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - await context.farmingCenter.connect(lpUser0).enterFarming(farmIncentiveKey, tokenId); - await context.eternalFarming.farms(tokenId, incentiveId); - }); - - it('returns correct rewardAmount and secondsInsideX128 for the position', async () => { - const pool = context.poolObj.connect(actors.lpUser0()); - - await Time.set(timestamps.startTime + 10); - //await provider.send('evm_mine', [timestamps.startTime + 100]) - const trader = actors.traderUser0(); - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 10, - }); - - await Time.set(timestamps.endTime - 10); - - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 100, - }); - - await Time.set(timestamps.endTime + 10); - - const rewardInfo = await context.eternalFarming.connect(lpUser0).getRewardInfo(farmIncentiveKey, tokenId); - - const { tickLower, tickUpper } = await context.nft.positions(tokenId); - const farm = await context.eternalFarming.farms(tokenId, incentiveId); - - // @ts-ignore - expect(rewardInfo.reward).to.be.closeTo(BN('9900'), BN('10000')); - //expect(rewardInfo.secondsInsideX128).to.equal(expectedSecondsInPeriod) - }); - - it('reverts if farm does not exist', async () => { - // await Time.setAndMine(timestamps.endTime + 1) - - await expect(context.eternalFarming.connect(lpUser0).getRewardInfo(farmIncentiveKey, '100')).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'farmDoesNotExist' - ); - }); - }); - - describe('#decreaseRewards', () => { - let incentiveArgs: HelperTypes.CreateIncentive.Args; - let incentiveKey: ContractParams.IncentiveKey; - let virtualPool: EternalVirtualPool; - - let factoryOwner: Wallet; - - beforeEach('set up incentive and farm', async () => { - factoryOwner = actors.wallets[0]; - incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: context.pool01, - nonce: 0n, - rewardRate: 10000n, - bonusRewardRate: 50000n, - }; - - incentiveKey = { - nonce: 0n, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - - pool: context.pool01, - }; - - const vpFactory = await ethers.getContractFactory('EternalVirtualPool'); - const createIncentiveResult = await helpers.createIncentiveFlow(incentiveArgs); - const _vpool = createIncentiveResult.virtualPool; - virtualPool = vpFactory.attach(_vpool) as any as EternalVirtualPool; - }); - - it('onlyOwner', async () => { - expect(context.eternalFarming.connect(lpUser0).decreaseRewardsAmount(incentiveKey, 100, 100)).to.be.revertedWithoutReason; - }); - - it('can decrease rewards before start', async () => { - await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 100, 100)) - .to.emit(context.rewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) - .to.emit(context.bonusRewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) - .to.emit(context.eternalFarming, 'RewardAmountsDecreased'); - - const reserves = await virtualPool.rewardReserves(); - - expect(reserves[0]).to.be.eq(totalReward - 100n); - expect(reserves[1]).to.be.eq(bonusReward - 100n); - }); - - it('can decrease only main reward', async () => { - await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 100, 0)) - .to.emit(context.eternalFarming, 'RewardAmountsDecreased') - .to.emit(context.rewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) - .to.not.emit(context.bonusRewardToken, 'Transfer'); - - const reserves = await virtualPool.rewardReserves(); - - expect(reserves[0]).to.be.eq(totalReward - 100n); - expect(reserves[1]).to.be.eq(bonusReward); - }); - - it('can decrease only bonus reward', async () => { - await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 0, 100)) - .to.emit(context.eternalFarming, 'RewardAmountsDecreased') - .to.emit(context.bonusRewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) - .to.not.emit(context.rewardToken, 'Transfer'); - - const reserves = await virtualPool.rewardReserves(); - - expect(reserves[0]).to.be.eq(totalReward); - expect(reserves[1]).to.be.eq(bonusReward - 100n); - }); - - it('cannot exceed reserves', async () => { - await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, totalReward + 1n, bonusReward + 1n)) - .to.emit(context.rewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, totalReward - 1n) - .to.emit(context.bonusRewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, bonusReward) - .to.emit(context.eternalFarming, 'RewardAmountsDecreased'); - }); - - it('max uint128', async () => { - await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 2n ** 128n - 1n, 2n ** 128n - 1n)) - .to.emit(context.rewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, totalReward - 1n) - .to.emit(context.bonusRewardToken, 'Transfer') - .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, bonusReward) - .to.emit(context.eternalFarming, 'RewardAmountsDecreased'); - }); - - it('decrease with 0 amount', async () => { - await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 0, 0)); - }); - }); - - describe('#deactivate incentive', () => { - let incentiveArgs: HelperTypes.CreateIncentive.Args; - let incentiveKey: ContractParams.IncentiveKey; - let virtualPool: EternalVirtualPool; - let virtualPoolAddress: string; - - let localNonce = 0n; - - let factoryOwner: Wallet; - - beforeEach('set up incentive and farm', async () => { - localNonce = await context.eternalFarming.numOfIncentives(); - factoryOwner = actors.wallets[0]; - incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: context.pool01, - nonce: localNonce, - rewardRate: 10000n, - bonusRewardRate: 50000n, - }; - - incentiveKey = { - nonce: localNonce, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - - pool: context.pool01, - }; - - const vpFactory = await ethers.getContractFactory('EternalVirtualPool'); - const createIncentiveResult = await helpers.createIncentiveFlow(incentiveArgs); - const _vpool = createIncentiveResult.virtualPool; - virtualPool = vpFactory.attach(_vpool) as any as EternalVirtualPool; - virtualPoolAddress = await virtualPool.getAddress(); - }); - - it('deactivate incentive', async () => { - let activeIncentiveBefore = await context.pluginObj.incentive(); - - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - let activeIncentiveAfter = await context.pluginObj.incentive(); - - expect(activeIncentiveBefore).to.equal(await virtualPool.getAddress()); - expect(activeIncentiveAfter).to.equal(ZERO_ADDRESS); - - const rewardRates = await virtualPool.rewardRates(); - - expect(rewardRates[0]).to.be.eq(0); - expect(rewardRates[1]).to.be.eq(0); - }); - - it('deactivate incentive with zero rates', async () => { - let activeIncentiveBefore = await context.pluginObj.incentive(); - - await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 0, 0); - - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - let activeIncentiveAfter = await context.pluginObj.incentive(); - - expect(activeIncentiveBefore).to.equal(virtualPoolAddress); - expect(activeIncentiveAfter).to.equal(ZERO_ADDRESS); - - const rewardRates = await virtualPool.rewardRates(); - - expect(rewardRates[0]).to.be.eq(0); - expect(rewardRates[1]).to.be.eq(0); - }); - - it('deactivate incentive only incentiveMaker', async () => { - let activeIncentiveBefore = await context.pluginObj.incentive(); - - expect(context.eternalFarming.connect(lpUser0).deactivateIncentive(incentiveKey)).to.be.revertedWithoutReason; - let activeIncentiveAfter = await context.pluginObj.incentive(); - - expect(activeIncentiveBefore).to.equal(virtualPoolAddress); - expect(activeIncentiveAfter).to.equal(virtualPoolAddress); - }); - - it('cannot deactivate twice', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.be.revertedWithCustomError( - context.eternalFarming, - 'incentiveStopped' - ); - }); - - it('cannot deactivate nonexistent incentive', async () => { - const invalidKey = { ...incentiveKey }; - invalidKey.nonce = 999; - await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(invalidKey)).to.be.revertedWithCustomError( - context.eternalFarming, - 'incentiveNotExist' - ); - }); - - it('can deactivate manually after indirect deactivation', async () => { - await detachIncentiveIndirectly(localNonce); - await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.not.be.reverted; - }); - - it('can deactivate manually after indirect deactivation and exit', async () => { - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - const tokenIdNarrow = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: 120, - tickUpper: 180, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId: tokenIdNarrow, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenIdNarrow - ); - - const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); - const virtualPoolFactory = await ethers.getContractFactory('EternalVirtualPool'); - const virtualPool = virtualPoolFactory.attach(incentiveAddress) as any as EternalVirtualPool; - - expect(await virtualPool.deactivated()).to.be.false; - const incentiveId = await helpers.getIncentiveId({ - poolAddress: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - virtualPool: virtualPool as any as Contract, - nonce: localNonce, - bonusReward: 0n, - totalReward: 0n, - }); - expect((await context.eternalFarming.incentives(incentiveId)).deactivated).to.be.false; - - await detachIncentiveIndirectly(localNonce); - - await context.farmingCenter.connect(lpUser0).exitFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenIdNarrow - ); - - expect(await virtualPool.deactivated()).to.be.true; - expect((await context.eternalFarming.incentives(incentiveId)).deactivated).to.be.false; - - await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.not.be.reverted; - - expect(await virtualPool.deactivated()).to.be.true; - expect((await context.eternalFarming.incentives(incentiveId)).deactivated).to.be.true; - }); - - it('can deactivate manually if farming detached manually from plugin', async () => { - await context.pluginFactory.setFarmingAddress(incentiveCreator.address); - await context.pluginObj.connect(incentiveCreator).setIncentive(ZERO_ADDRESS); - - await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.not.be.reverted; - }); - - it('cross lower after deactivate', async () => { - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 2n, await context.nft.getAddress()); - - await erc20Helper.ensureBalancesAndApprovals( - incentiveCreator, - [context.rewardToken, context.bonusRewardToken], - BNe18(1), - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(incentiveCreator).addRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce: 0, - }, - BNe18(1), - BNe18(1) - ); - - const tokenIdWide = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - const tokenIdNarrow = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: 120, - tickUpper: 180, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId: tokenIdWide, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: 0, - }, - tokenIdWide - ); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId: tokenIdNarrow, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: 0, - }, - tokenIdNarrow - ); - - await helpers.moveTickTo({ direction: 'up', desiredValue: 150, trader: actors.farmingDeployer() }); - - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - - await helpers.moveTickTo({ direction: 'down', desiredValue: -200, trader: actors.farmingDeployer() }); - - await context.farmingCenter.connect(lpUser0).collectRewards(incentiveKey, tokenIdNarrow); - let rewards = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusRewards = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - let vpTick = await virtualPool.globalTick(); - expect(rewards).to.eq(9970); - expect(bonusRewards).to.eq(49851); - expect(vpTick).to.eq(150); - }); - - it('cross upper after deactivate', async () => { - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 2n, await context.nft.getAddress()); - - await erc20Helper.ensureBalancesAndApprovals( - incentiveCreator, - [context.rewardToken, context.bonusRewardToken], - BNe18(1), - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(incentiveCreator).addRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce: 0, - }, - BNe18(1), - BNe18(1) - ); - - const tokenIdWide = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - const tokenIdNarrow = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: -180, - tickUpper: -120, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId: tokenIdWide, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: 0, - }, - tokenIdWide - ); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId: tokenIdNarrow, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: 0, - }, - tokenIdNarrow - ); - - await helpers.moveTickTo({ direction: 'down', desiredValue: -150, trader: actors.farmingDeployer() }); - - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - - await helpers.moveTickTo({ direction: 'up', desiredValue: 200, trader: actors.farmingDeployer() }); - - await context.farmingCenter.connect(lpUser0).collectRewards(incentiveKey, tokenIdNarrow); - let rewards = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusRewards = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - let vpTick = await virtualPool.globalTick(); - expect(rewards).to.eq(9970); - expect(bonusRewards).to.eq(49851); - expect(vpTick).to.eq(-150); - }); - }); - - describe('#claimReward', () => { - let createIncentiveResult: HelperTypes.CreateIncentive.Result; - let subject: (token: string | TestERC20, to: string, amount: bigint) => Promise; - // The amount the user should be able to claim - let claimable: bigint; - let localNonce = 0n; - - beforeEach('setup', async () => { - timestamps = makeTimestamps(await blockTimestamp()); - const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); - - localNonce = await context.eternalFarming.numOfIncentives(); - - createIncentiveResult = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - - await Time.setAndMine(timestamps.startTime + 100); - - const mintResult = await helpers.mintDepositFarmFlow({ - lp: lpUser0, - tokensToFarm, - ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], - amountsToFarm: [amountDesired, amountDesired], - createIncentiveResult, - }); - tokenId = mintResult.tokenId; - - await Time.setAndMine(timestamps.endTime + 10); - await context.farmingCenter.connect(lpUser0).exitFarming( - { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: localNonce, - }, - tokenId - ); - - claimable = await context.eternalFarming.rewards(lpUser0.address, await context.rewardToken.getAddress()); - - subject = (_token: string | TestERC20, _to: string, _amount: bigint) => - context.eternalFarming.connect(lpUser0).claimReward(_token, _to, _amount); - }); - - describe('when requesting the full amount', () => { - it('emits RewardClaimed event', async () => { - const { rewardToken } = context; - claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); - await expect(subject(rewardToken, lpUser0.address, BN('0'))) - .to.emit(context.eternalFarming, 'RewardClaimed') - .withArgs(lpUser0.address, claimable, await context.rewardToken.getAddress(), lpUser0.address); - }); - - it('transfers the correct reward amount to destination address', async () => { - const { rewardToken } = context; - claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); - const balance = await rewardToken.balanceOf(lpUser0.address); - await subject(rewardToken, lpUser0.address, BN('0')); - expect(await rewardToken.balanceOf(lpUser0.address)).to.equal(balance + claimable); - }); - - it('sets the claimed reward amount to zero', async () => { - const { rewardToken } = context; - expect(await context.eternalFarming.rewards(lpUser0.address, rewardToken)).to.not.equal(0); - - await subject(rewardToken, lpUser0.address, BN('0')); - - expect(await context.eternalFarming.rewards(lpUser0.address, rewardToken)).to.equal(0); - }); - - it('has gas cost [ @skip-on-coverage ]', async () => - await snapshotGasCost(subject(await context.rewardToken.getAddress(), lpUser0.address, BN('0')))); - - it('returns their claimable amount', async () => { - const { rewardToken, eternalFarming } = context; - const amountBefore = await rewardToken.balanceOf(lpUser0.address); - await subject(rewardToken, lpUser0.address, BN('0')); - expect(await eternalFarming.rewards(lpUser0.address, rewardToken)).to.eq(BN('0')); - expect(await rewardToken.balanceOf(lpUser0.address)).to.eq(amountBefore + claimable); - }); - }); - - describe('when requesting a nonzero amount', () => { - it('emits RewardClaimed event', async () => { - const { rewardToken } = context; - await expect(subject(rewardToken, lpUser0.address, claimable)) - .to.emit(context.eternalFarming, 'RewardClaimed') - .withArgs(lpUser0.address, claimable, await context.rewardToken.getAddress(), lpUser0.address); - }); - - it('transfers the correct reward amount to destination address', async () => { - const { rewardToken } = context; - claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); - const balance = await rewardToken.balanceOf(lpUser0.address); - await subject(rewardToken, lpUser0.address, claimable); - expect(await rewardToken.balanceOf(lpUser0.address)).to.equal(balance + claimable); - }); - - it('reverts if transfer to zero', async () => { - const { rewardToken } = context; - claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); - await expect(subject(rewardToken, ZERO_ADDRESS, claimable)).to.be.revertedWithCustomError(context.eternalFarming, 'claimToZeroAddress'); - }); - - it('sets the claimed reward amount to the correct amount', async () => { - const { rewardToken, eternalFarming } = context; - const initialRewardBalance = await eternalFarming.rewards(lpUser0.address, rewardToken); - expect(initialRewardBalance).to.not.equal(BN('0')); - - const partialClaim = initialRewardBalance / BN('3'); - await subject(rewardToken, lpUser0.address, partialClaim); - - expect(await eternalFarming.rewards(lpUser0.address, rewardToken)).to.eq(initialRewardBalance - partialClaim); - }); - - it('not emit event if nothing to claim', async () => { - const { rewardToken } = context; - await expect(context.eternalFarming.connect(actors.lpUser2()).claimReward(rewardToken, actors.lpUser2().address, 100n)).to.not.emit( - context.eternalFarming, - 'RewardClaimed' - ); - }); - - describe('when user claims more than they have', () => { - it('only transfers what they have', async () => { - const { rewardToken, eternalFarming } = context; - const amountBefore = await rewardToken.balanceOf(lpUser0.address); - await subject(rewardToken, lpUser0.address, claimable * 3n); - expect(await eternalFarming.rewards(lpUser0.address, rewardToken)).to.eq(BN('0')); - expect(await rewardToken.balanceOf(lpUser0.address)).to.eq(amountBefore + claimable); - }); - }); - }); - }); - - describe('#collectRewards', async () => { - let createIncentiveResult: HelperTypes.CreateIncentive.Result; - let localNonce = 0n; - - beforeEach('setup', async () => { - timestamps = makeTimestamps(await blockTimestamp()); - const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); - - localNonce = await context.eternalFarming.numOfIncentives(); - - createIncentiveResult = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - - await Time.setAndMine(timestamps.startTime + 100); - - const mintResult = await helpers.mintDepositFarmFlow({ - lp: lpUser0, - tokensToFarm, - ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], - amountsToFarm: [amountDesired, amountDesired], - createIncentiveResult, - }); - tokenId = mintResult.tokenId; - - await Time.setAndMine(timestamps.endTime + 10); - - await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(lpUser0); - }); - - it('cannot collect from nonexistent farm', async () => { - const invalidTokenId = 9999n; - await expect( - context.eternalFarming.connect(lpUser0).collectRewards( - { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: localNonce, - }, - invalidTokenId, - lpUser0.address - ) - ).to.be.revertedWithCustomError(context.eternalFarming, 'farmDoesNotExist'); - }); - - it('do not update rewards if nothing to collect', async () => { - let rewardTokenAddress = await context.rewardToken.getAddress() - let bonusRewardTokenAddress = await context.bonusRewardToken.getAddress() - - await context.eternalFarming.connect(actors.wallets[0]).setRates( - { - rewardToken: rewardTokenAddress, - bonusRewardToken: bonusRewardTokenAddress, - pool: context.pool01, - nonce: localNonce, - }, - 0, - 0 - ); - - await context.eternalFarming.connect(lpUser0).collectRewards( - { - rewardToken: rewardTokenAddress, - bonusRewardToken: bonusRewardTokenAddress, - pool: context.pool01, - nonce: localNonce, - }, - tokenId, - lpUser0.address - ); - - const rewardTokenBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, rewardTokenAddress); - const bonusRewardTokenBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, bonusRewardTokenAddress); - - await context.eternalFarming.connect(lpUser0).collectRewards( - { - rewardToken: rewardTokenAddress, - bonusRewardToken: bonusRewardTokenAddress, - pool: context.pool01, - nonce: localNonce, - }, - tokenId, - lpUser0.address - ); - - const rewardTokenBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, rewardTokenAddress); - const bonusRewardTokenBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, bonusRewardTokenAddress); - - - expect(rewardTokenBalanceAfter).to.be.eq(rewardTokenBalanceBefore); - expect(bonusRewardTokenBalanceAfter).to.be.eq(bonusRewardTokenBalanceBefore); - }); - }); - - describe('#exitFarming', () => { - let incentiveId: string; - let subject: (actor: Wallet) => Promise; - let createIncentiveResult: HelperTypes.CreateIncentive.Result; - - let localNonce = 0n; - - describe('before end time', () => { - it('can exitFarming', async () => { - timestamps = makeTimestamps(await blockTimestamp()); - - localNonce = await context.eternalFarming.numOfIncentives(); - - createIncentiveResult = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: context.token0, - token1: context.token1, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - // await Time.setAndMine(timestamps.startTime + 1) - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: localNonce, - }, - tokenId - ); - - incentiveId = await helpers.getIncentiveId(createIncentiveResult); - - await expect( - context.farmingCenter.connect(actors.lpUser0()).exitFarming( - { - pool: context.pool01, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - nonce: localNonce, - }, - tokenId - ) - ).to.be.emit(context.eternalFarming, 'FarmEnded'); - }); - }); - - describe('after end time', () => { - let tokenIdOut: string; - beforeEach('create the incentive and nft and farm it', async () => { - timestamps = makeTimestamps(await blockTimestamp()); - - localNonce = await context.eternalFarming.numOfIncentives(); - - createIncentiveResult = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 3n, await context.nft.getAddress()); - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: context.token0, - token1: context.token1, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - tokenIdOut = await mintPosition(context.nft.connect(lpUser0), { - token0: context.token0, - token1: context.token1, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]) + TICK_SPACINGS[FeeAmount.MEDIUM], - recipient: lpUser0.address, - amount0Desired: 0, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 10000, - }); - - await Time.setAndMine(timestamps.startTime + 100); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - await context.nft.connect(lpUser0).approveForFarming(tokenIdOut, true, context.farmingCenter); - await context.farmingCenter.connect(lpUser0).enterFarming( - { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: localNonce, - }, - tokenId - ); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: localNonce, - }, - tokenIdOut - ); - - await Time.setAndMine(timestamps.endTime + 10); - - incentiveId = await helpers.getIncentiveId(createIncentiveResult); - subject = (_actor: Wallet) => - context.farmingCenter.connect(_actor).exitFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - }); - - describe('works and', () => { - it('emits an exitFarmingd event', async () => { - await expect(subject(lpUser0)) - .to.emit(context.eternalFarming, 'FarmEnded') - .withArgs( - tokenId, - incentiveId, - await context.rewardToken.getAddress(), - await context.bonusRewardToken.getAddress(), - lpUser0.address, - 9079n, - 199n - ); - }); - - it('has gas cost [ @skip-on-coverage ]', async () => { - await snapshotGasCost(subject(lpUser0)); - }); - - it('updates the reward available for the context.tokenomics', async () => { - const rewardsAccured = await context.eternalFarming.rewards(lpUser0.address, await context.rewardToken.getAddress()); - await subject(lpUser0); - expect(await context.eternalFarming.rewards(lpUser0.address, await context.rewardToken.getAddress())).to.be.gt(rewardsAccured); - }); - - it('updates the farm struct', async () => { - const farmBefore = await context.eternalFarming.farms(tokenId, incentiveId); - await subject(lpUser0); - const farmAfter = await context.eternalFarming.farms(tokenId, incentiveId); - - expect(farmBefore.liquidity).to.gt(0); - expect(farmAfter.liquidity).to.eq(0); - }); - }); - - it('can exit without rewards', async () => { - await expect( - context.farmingCenter.connect(lpUser0).exitFarming( - { - pool: context.pool01, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - nonce: localNonce, - }, - tokenIdOut - ) - ) - .to.emit(context.eternalFarming, 'FarmEnded') - .withArgs( - tokenIdOut, - incentiveId, - await context.rewardToken.getAddress(), - await context.bonusRewardToken.getAddress(), - lpUser0.address, - BN('0'), - BN('0') - ); - }); - - it('cannot exit twice', async () => { - await subject(lpUser0); - await expect( - context.farmingCenter.connect(lpUser0).exitFarming( - { - pool: context.pool01, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - nonce: localNonce, - }, - tokenId - ) - ).to.be.revertedWith('Invalid incentiveId'); - }); - - it('cannot exit from nonexistent farming', async () => { - await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(lpUser0); - - await expect( - context.eternalFarming.connect(lpUser0).exitFarming( - { - pool: context.pool12, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - nonce: localNonce, - }, - tokenId, - lpUser0.address - ) - ).to.be.revertedWithCustomError(context.eternalFarming, 'farmDoesNotExist'); - }); - - it('can exit from deactivated farming', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }); - await subject(lpUser0); - }); - - it('can exit from indirectly deactivated farming', async () => { - await detachIncentiveIndirectly(localNonce); - await subject(lpUser0); - }); - - //it('calculates the right secondsPerLiquidity') - //it('does not overflow totalSecondsUnclaimed') - }); - - it('rewards calculation underflow', async () => { - const helpers = HelperCommands.fromTestContext(context, actors, provider); - - const localNonce = await context.eternalFarming.numOfIncentives(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 3n, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - const incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward: 1000000n, - bonusReward: 1000000n, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }; - - const incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - - const trader = actors.traderUser0(); - - await helpers.makeTickGoFlow({ - trader, - direction: 'down', - desiredValue: -30, - }); - - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 0, - }); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: -120, - tickUpper: -60, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - - await helpers.makeTickGoFlow({ - trader, - direction: 'down', - desiredValue: -150, - }); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: -240, - tickUpper: -60, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - - let time = await blockTimestamp(); - - await Time.set(time + 10000); - - await helpers.makeTickGoFlow({ - trader, - direction: 'down', - desiredValue: -238, - }); - - await context.farmingCenter.connect(lpUser0).exitFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: localNonce, - }, - tokenId - ); - }); - - describe('fails if', () => { - it('farm has already been exitFarming', async () => { - await expect(subject(lpUser0)).to.revertedWith('ERC721: invalid token ID'); - }); - - it('if reentrancy lock in pool is locked', async () => { - const _factory = await ethers.getContractFactory('TestERC20Reentrant'); - const tokenReentrant = (await _factory.deploy(MaxUint256 / 2n)) as any as TestERC20Reentrant; - - const [token0, token1] = - (await tokenReentrant.getAddress()) < (await context.token1.getAddress()) - ? [tokenReentrant, context.token1] - : [context.token1, tokenReentrant]; - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [token0, token1], amountDesired, await context.nft.getAddress()); - - await context.nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); - - const poolAddress = await context.factory.poolByPair(token0, token1); - - const _tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: token0, - token1: token1, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - const _nonce = await context.eternalFarming.numOfIncentives(); - - await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress, - nonce: _nonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - - await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: poolAddress, - nonce: _nonce, - }, - _tokenId - ); - - //await tokenReentrant.prepareAttack(incentiveKey, 500, 500); - - await context.nft.connect(lpUser0).approve(tokenReentrant, _tokenId); - const txData = await context.farmingCenter.exitFarming.populateTransaction( - { - pool: poolAddress, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: _nonce, - }, - _tokenId - ); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [token0, token1], amountDesired, await context.router.getAddress()); - const swapData = { - tokenIn: tokenReentrant, - tokenOut: context.token1, - deployer: ZERO_ADDRESS, - amountIn: 10, - amountOutMinimum: 0, - recipient: lpUser0.address, - deadline: (await blockTimestamp()) + 10000, - limitSqrtPrice: 0, - }; - - await tokenReentrant.prepareComplexAttack(context.farmingCenter, txData.data); - await expect(context.router.connect(lpUser0).exactInputSingle(swapData)).to.be.revertedWith('STF'); - - await tokenReentrant.cancelComplexAttack(); - await expect(context.router.connect(lpUser0).exactInputSingle(swapData)).to.be.not.reverted; - }); - }); - }); - - describe('liquidityIfOverflow', () => { - const MAX_UINT_96 = 2n ** 96n - 1n; - - let incentive: HelperTypes.CreateIncentive.Result; - let incentiveId: string; - let localNonce = 0n; - - beforeEach(async () => { - timestamps = makeTimestamps(1_000 + (await blockTimestamp())); - localNonce = await context.eternalFarming.numOfIncentives(); - incentive = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - incentiveId = await helpers.getIncentiveId(incentive); - // await Time.setAndMine(timestamps.startTime + 1) - }); - - it('works when no overflow', async () => { - // With this `amount`, liquidity ends up less than MAX_UINT96 - const amount = MAX_UINT_96 / 1000n; - - const { tokenId } = await helpers.mintFlow({ - lp: lpUser0, - tokens: [context.token0, context.token1], - amounts: [amount, amount], - tickLower: 0, - tickUpper: 10 * TICK_SPACINGS[FeeAmount.MEDIUM], - }); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming(await incentiveResultToFarmAdapter(incentive), tokenId); - const farm = await context.eternalFarming.farms(tokenId, incentiveId); - expect(farm.liquidity).to.be.lt(MAX_UINT_96); - }); - - it('works when overflow', async () => { - // With this `amount`, liquidity ends up more than MAX_UINT96 - const amount = MAX_UINT_96 - 100n; - const { tokenId } = await helpers.mintFlow({ - lp: lpUser0, - tokens: [context.token0, context.token1], - amounts: [amount, amount], - tickLower: 0, - tickUpper: 10 * TICK_SPACINGS[FeeAmount.MEDIUM], - }); - - await helpers.depositFlow({ - lp: lpUser0, - tokenId, - }); - - await context.farmingCenter.connect(lpUser0).enterFarming(await incentiveResultToFarmAdapter(incentive), tokenId); - const farm = await context.eternalFarming.farms(tokenId, incentiveId); - expect(farm.liquidity).to.be.gt(MAX_UINT_96); - }); - }); - - describe('#rewards', async () => { - let incentiveArgs: HelperTypes.CreateIncentive.Args; - let incentiveKey: ContractParams.IncentiveKey; - let incentiveId: string; - - let localNonce = 0n; - - beforeEach(async () => { - /** We will be doing a lot of time-testing here, so leave some room between - and when the incentive starts */ - bonusReward = BN(10000); - timestamps = makeTimestamps(1_000 + (await blockTimestamp())); - - localNonce = await context.eternalFarming.numOfIncentives(); - - incentiveArgs = { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce: localNonce, - rewardRate: 100n, - bonusRewardRate: 3n, - }; - - incentiveKey = { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool01, - nonce: localNonce, - }; - - incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); - }); - - describe('#addRewards', async () => { - it('can add rewards', async () => { - let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - await erc20Helper.ensureBalancesAndApprovals( - lpUser0, - [context.rewardToken, context.bonusRewardToken], - amountDesired, - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired); - - let incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - expect(incentiveAfter.totalReward - amountDesired).to.eq(incentiveBefore.totalReward); - expect(incentiveAfter.bonusReward - amountDesired).to.eq(incentiveBefore.bonusReward); - }); - - it('can add rewards in deflationary token', async () => { - await context.rewardToken.setDefl(true, 5); - - let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - await erc20Helper.ensureBalancesAndApprovals( - lpUser0, - [context.rewardToken, context.bonusRewardToken], - amountDesired * 2n, - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired); - - let incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - expect(incentiveAfter.totalReward).to.be.gt(incentiveBefore.totalReward); - expect(incentiveAfter.bonusReward - amountDesired).to.eq(incentiveBefore.bonusReward); - }); - - it('cannot add rewards if token does incorrect transfer', async () => { - await erc20Helper.ensureBalancesAndApprovals( - lpUser0, - [context.rewardToken, context.bonusRewardToken], - 2n ** 128n + 10n, - await context.eternalFarming.getAddress() - ); - - await context.rewardToken.setDefl(true, 100); - - expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired)).to.be.revertedWithoutReason; - - await context.rewardToken.setDefl(false, 0); - - await context.rewardToken.setNextTransferAmount(2n ** 128n); - - await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired)).to.be.revertedWithCustomError( - context.eternalFarming, - 'invalidTokenAmount' - ); - }); - - it('can add rewards with 0 amounts', async () => { - let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - await erc20Helper.ensureBalancesAndApprovals( - lpUser0, - [context.rewardToken, context.bonusRewardToken], - amountDesired, - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 0, 0); - - let incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - expect(incentiveAfter.totalReward).to.eq(incentiveBefore.totalReward); - expect(incentiveAfter.bonusReward).to.eq(incentiveBefore.bonusReward); - - await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 0, 1); - incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - expect(incentiveAfter.totalReward).to.eq(incentiveBefore.totalReward); - expect(incentiveAfter.bonusReward).to.eq(incentiveBefore.bonusReward + 1n); - - await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 1, 0); - incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - expect(incentiveAfter.totalReward).to.eq(incentiveBefore.totalReward + 1n); - expect(incentiveAfter.bonusReward).to.eq(incentiveBefore.bonusReward + 1n); - }); - - it('can add rewards with uint128 amounts', async () => { - let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); - - await erc20Helper.ensureBalancesAndApprovals( - lpUser0, - [context.rewardToken, context.bonusRewardToken], - 2n ** 128n, - await context.eternalFarming.getAddress() - ); - - let factoryOwner = actors.wallets[0]; - - await context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 10000n, 10000n); - await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 2n ** 128n - 1n, 2n ** 128n - 1n)).to.be.reverted; - }); - - it('cannot add rewards to non-existent incentive', async () => { - incentiveKey = { - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool12, - nonce: localNonce, - }; - - await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 0, 0)).to.be.revertedWithCustomError( - context.eternalFarming as AlgebraEternalFarming, - 'incentiveNotExist' - ); - }); - - it('cannot add rewards to deactivated incentive', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - - await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 1, 1)).to.be.revertedWithCustomError( - context.eternalFarming, - 'incentiveStopped' - ); - }); - - it('addRewards to indirectly deactivated incentive', async () => { - await detachIncentiveIndirectly(localNonce); - - await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 1, 1)).to.be.revertedWithCustomError( - context.eternalFarming, - 'incentiveStopped' - ); - }); - - it('cannot reenter to addRewards', async () => { - const _factory = await ethers.getContractFactory('TestERC20Reentrant'); - const tokenReentrant = (await _factory.deploy(MaxUint256 / 2n)) as any as TestERC20Reentrant; - - await erc20Helper.ensureBalancesAndApprovals( - lpUser0, - [tokenReentrant, context.bonusRewardToken], - amountDesired, - await context.eternalFarming.getAddress() - ); - - const nonce = await context.eternalFarming.numOfIncentives(); - await helpers.createIncentiveFlow({ - rewardToken: tokenReentrant, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: context.pool12, - nonce: nonce, - rewardRate: 10n, - bonusRewardRate: 50n, - }); - - const incentiveKey2 = { - rewardToken: await tokenReentrant.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - pool: context.pool12, - nonce: nonce, - }; - - await tokenReentrant.prepareAttack(incentiveKey, 500, 500); - - await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey2, 1, 1)).to.be.revertedWith('STF'); - }); - }); - - describe('#setRates', async () => { - it('can change rates', async () => { - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: context.token0, - token1: context.token1, - fee: FeeAmount.MEDIUM, - tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); - - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), - nonce: localNonce, - }, - tokenId - ); - await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, BN(60), BN(5)); - let rewardsBefore = await context.eternalFarming.getRewardInfo(incentiveKey, tokenId); - let time = await blockTimestamp(); - - await Time.set(time + 100); - - const trader = actors.traderUser0(); - - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 10, - }); - time = await blockTimestamp(); - let rewardsAfter = await context.eternalFarming.getRewardInfo(incentiveKey, tokenId); - - expect(rewardsAfter.reward - rewardsBefore.reward).to.eq(60n * 104n); - expect(rewardsAfter.bonusReward - rewardsBefore.bonusReward).to.eq(5n * 104n); - }); - - it('cannot set nonzero to deactivated incentive', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - - await expect(context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 1, 1)).to.be.revertedWithCustomError( - context.eternalFarming, - 'incentiveStopped' - ); - }); - - it('set zero to deactivated incentive', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); - await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 0, 0); - }); - - it('set max rates', async () => { - await expect(context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 2n ** 128n - 1n, 2n ** 128n - 1n)) - .to.emit(context.eternalFarming, 'RewardsRatesChanged') - .withArgs(2n ** 128n - 1n, 2n ** 128n - 1n, incentiveId); - }); - - it('cannot set nonzero to indirectly deactivated incentive', async () => { - await detachIncentiveIndirectly(localNonce); - - await expect(context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 1, 1)).to.be.revertedWithCustomError( - context.eternalFarming, - 'incentiveStopped' - ); - }); - - it('set zero to indirectly deactivated incentive', async () => { - await detachIncentiveIndirectly(localNonce); - - await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 0, 0); - }); - }); - }); -}); +import { ethers } from 'hardhat'; +import { Contract, Wallet, MaxUint256 } from 'ethers'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { TestERC20, AlgebraEternalFarming, EternalVirtualPool, TestERC20Reentrant } from '../../typechain'; +import { mintPosition, AlgebraFixtureType, algebraFixture } from '../shared/fixtures'; +import { + expect, + getMaxTick, + getMinTick, + FeeAmount, + TICK_SPACINGS, + blockTimestamp, + BN, + BNe18, + snapshotGasCost, + ActorFixture, + makeTimestamps, + ZERO_ADDRESS, + encodePriceSqrt, +} from '../shared'; +import { provider } from '../shared/provider'; +import { HelperCommands, ERC20Helper, incentiveResultToFarmAdapter } from '../helpers'; +import { ContractParams } from '../../types/contractParams'; +import { createTimeMachine } from '../shared/time'; +import { HelperTypes } from '../helpers/types'; + +describe('unit/EternalFarms', () => { + let actors: ActorFixture; + let lpUser0: Wallet; + let incentiveCreator: Wallet; + const amountDesired = BNe18(10); + const totalReward = 10000n; + const erc20Helper = new ERC20Helper(); + const Time = createTimeMachine(); + let nonce = BN(0); + let bonusReward = 200n; + let helpers: HelperCommands; + let context: AlgebraFixtureType; + let timestamps: ContractParams.Timestamps; + let tokenId: string; + + const detachIncentiveIndirectly = async (localNonce: any) => { + await context.pluginFactory.setFarmingAddress(actors.algebraRootUser().address); + + const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + const _tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: -120, + tickUpper: 120, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + await helpers.mintFlow({ + lp: lpUser0, + tokens: [context.token0, context.token1], + }); + + await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + _tokenId + ); + + await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(ZERO_ADDRESS); + + const tick = (await context.poolObj.connect(actors.algebraRootUser()).globalState()).tick; + + await helpers.moveTickTo({ direction: 'down', desiredValue: Number(tick) - 130, trader: actors.farmingDeployer() }); + + await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(incentiveAddress); + + await helpers.moveTickTo({ direction: 'up', desiredValue: Number(tick) - 125, trader: actors.farmingDeployer() }); + + await context.pluginFactory.setFarmingAddress(context.farmingCenter); + + const virtualPoolFactory = await ethers.getContractFactory('EternalVirtualPool'); + const deactivated = await (virtualPoolFactory.attach(incentiveAddress) as any as EternalVirtualPool).deactivated(); + expect(deactivated).to.be.true; + }; + + before(async () => { + const wallets = (await ethers.getSigners()) as any as Wallet[]; + actors = new ActorFixture(wallets, provider); + lpUser0 = actors.lpUser0(); + incentiveCreator = actors.incentiveCreator(); + }); + + beforeEach('load fixture', async () => { + context = await loadFixture(algebraFixture); + helpers = HelperCommands.fromTestContext(context, actors, provider); + }); + + describe('#onlyFarmingCenter ', () => { + const dummyKey = { + rewardToken: ZERO_ADDRESS, + bonusRewardToken: ZERO_ADDRESS, + pool: ZERO_ADDRESS, + nonce: 0, + }; + + it('reverts if not farmingCenter', async () => { + expect(context.eternalFarming.connect(actors.farmingDeployer()).claimRewardFrom(context.rewardToken, lpUser0.address, lpUser0.address, 100)).to + .be.revertedWithoutReason; + + expect(context.eternalFarming.connect(actors.farmingDeployer()).enterFarming(dummyKey, 1)).to.be.revertedWithoutReason; + + expect(context.eternalFarming.connect(actors.farmingDeployer()).exitFarming(dummyKey, 1, ZERO_ADDRESS)).to.be.revertedWithoutReason; + + expect(context.eternalFarming.connect(actors.farmingDeployer()).collectRewards(dummyKey, 1, ZERO_ADDRESS)).to.be.revertedWithoutReason; + }); + }); + + describe('#onlyIncentiveMaker', () => { + const dummyKey = { + rewardToken: ZERO_ADDRESS, + bonusRewardToken: ZERO_ADDRESS, + pool: ZERO_ADDRESS, + nonce: 0, + }; + + it('reverts if not incentiveMaker', async () => { + expect( + context.eternalFarming.connect(actors.farmingDeployer()).createEternalFarming( + dummyKey, + { + reward: 100, + bonusReward: 100, + rewardRate: 100, + bonusRewardRate: 100, + minimalPositionWidth: 100, + }, + await context.poolObj.connect(incentiveCreator).plugin() + ) + ).to.be.revertedWithoutReason; + + expect(context.eternalFarming.connect(actors.farmingDeployer()).deactivateIncentive(dummyKey)).to.be.revertedWithoutReason; + + expect(context.eternalFarming.connect(actors.farmingDeployer()).setRates(dummyKey, 10, 10)).to.be.revertedWithoutReason; + }); + }); + + describe('#setFarmingCenterAddress', async () => { + beforeEach(async () => { + context = await loadFixture(algebraFixture); + }); + + it('only administrator', async () => { + expect(context.eternalFarming.connect(actors.lpUser0()).setFarmingCenterAddress(ZERO_ADDRESS)).to.be.revertedWithoutReason; + }); + + it('cannot set the same farming center', async () => { + expect(context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(context.farmingCenter)).to.be.revertedWithoutReason; + }); + + it('can set new farming center', async () => { + await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(ZERO_ADDRESS); + expect(await context.eternalFarming.farmingCenter()).to.be.eq(ZERO_ADDRESS); + }); + }); + + describe('#EmergencyWithdraw', async () => { + beforeEach(async () => { + context = await loadFixture(algebraFixture); + }); + + it('only administrator', async () => { + expect(context.eternalFarming.connect(actors.lpUser0()).setEmergencyWithdrawStatus(true)).to.be.revertedWithoutReason; + }); + + it('cannot set the current value', async () => { + expect(context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(false)).to.be.revertedWithoutReason; + }); + + it('can set new value', async () => { + await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); + expect(await context.eternalFarming.isEmergencyWithdrawActivated()).to.be.eq(true); + }); + + it('can not enter in farming if emergency', async () => { + const helpers = HelperCommands.fromTestContext(context, actors, provider); + + const localNonce = await context.eternalFarming.numOfIncentives(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + const incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + + await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await expect( + context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ) + ).to.be.revertedWithCustomError(context.eternalFarming, 'emergencyActivated'); + }); + + it('can exit from farming if emergency', async () => { + const helpers = HelperCommands.fromTestContext(context, actors, provider); + + const localNonce = await context.eternalFarming.numOfIncentives(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + const incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + + await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); + + await context.farmingCenter.connect(lpUser0).exitFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + }); + }); + + describe('#isIncentiveDeactivated', async () => { + let localNonce = 0n; + let incentiveArgs; + let incentiveId: string; + + beforeEach(async () => { + context = await loadFixture(algebraFixture); + helpers = HelperCommands.fromTestContext(context, actors, provider); + + localNonce = await context.eternalFarming.numOfIncentives(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + }); + + it('false if incentive active', async () => { + expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.false; + }); + + it('true if incentive deactivated', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }); + expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.true; + }); + + it('true if incentive deactivated indirectly', async () => { + await detachIncentiveIndirectly(localNonce); + + expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.true; + }); + }); + + describe('#incentiveKeys', async () => { + let localNonce = 0n; + let incentiveArgs; + let incentiveId: string; + let incentiveKey; + + beforeEach(async () => { + context = await loadFixture(algebraFixture); + helpers = HelperCommands.fromTestContext(context, actors, provider); + + localNonce = await context.eternalFarming.numOfIncentives(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + incentiveKey = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: await context.poolObj.getAddress(), + nonce: localNonce, + } + + incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + }); + + it('returns incentive key after farming creation', async () => { + let {rewardToken, bonusRewardToken, pool, nonce} = await context.eternalFarming.incentiveKeys(await context.poolObj.getAddress()) + expect(rewardToken, bonusRewardToken, pool, nonce).to.be.eq(incentiveKey.rewardToken, incentiveKey.bonusRewardToken, incentiveKey.pool, incentiveKey.nonce); + }); + + it('returns zero key after farming deactivation', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }); + + let {rewardToken, bonusRewardToken, pool, nonce} = await context.eternalFarming.incentiveKeys(await context.poolObj.getAddress()) + expect(rewardToken, bonusRewardToken, pool, nonce).to.be.eq(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, 0); + }); + + it('true if incentive deactivated indirectly', async () => { + await detachIncentiveIndirectly(localNonce); + + expect(await context.eternalFarming.isIncentiveDeactivated(incentiveId)).to.be.true; + }); + }); + + describe('#createEternalFarming', () => { + let localNonce = 0n; + + beforeEach(async () => { + context = await loadFixture(algebraFixture); + helpers = HelperCommands.fromTestContext(context, actors, provider); + + /** We will be doing a lot of time-testing here, so leave some room between + and when the incentive starts */ + localNonce = await context.eternalFarming.numOfIncentives(); + }); + + it('cannot create farming without rewards', async () => { + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward: 0n, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'zeroRewardAmount' + ); + }); + + it('cannot create farming if plugin is not connected', async () => { + await context.poolObj.connect(actors.wallets[0]).setPlugin(ZERO_ADDRESS); + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward: 10n, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'pluginNotConnected' + ); + }); + + it('cannot create farming if incorrect plugin is connected', async () => { + await context.poolObj.connect(actors.wallets[0]).setPlugin(actors.wallets[1].address); + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward: 10n, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + plugin: actors.wallets[0].address, + }; + + await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'pluginNotConnected' + ); + }); + + it('cannot set too wide minimal position width', async () => { + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + minimalPositionWidth: 2 ** 24 - 1, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'minimalPositionWidthTooWide' + ); + }); + + it('cannot create second eternal farming for one pool', async () => { + const incentiveArgsBase = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + await helpers.createIncentiveFlow(incentiveArgsBase); + + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce + 1n, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + await expect(helpers.createIncentiveFlow(incentiveArgs)).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'anotherFarmingIsActive' + ); + }); + }); + + describe('#enterFarming', () => { + let incentiveId: string; + let incentiveArgs: HelperTypes.CreateIncentive.Args; + let subject: (L2TokenId: string, _actor: Wallet) => Promise; + let localNonce = 0n; + + beforeEach(async () => { + context = await loadFixture(algebraFixture); + helpers = HelperCommands.fromTestContext(context, actors, provider); + + localNonce = await context.eternalFarming.numOfIncentives(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + subject = (L2TokenId: string, _actor: Wallet) => + context.farmingCenter.connect(_actor).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + L2TokenId + ); + }); + + describe('increaseLiqudity', () => { + it('liquidity updated correct', async () => { + await subject(tokenId, lpUser0); + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + let farmBefore = await context.eternalFarming.farms(tokenId, incentiveId); + await context.nft.connect(lpUser0).increaseLiquidity({ + tokenId: tokenId, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + let farmAfter = await context.eternalFarming.farms(tokenId, incentiveId); + expect(farmAfter.liquidity - farmBefore.liquidity).to.eq(amountDesired); + }); + }); + + describe('works and', () => { + it('emits the farm event', async () => { + const { liquidity } = await context.nft.positions(tokenId); + await expect(subject(tokenId, lpUser0)).to.emit(context.eternalFarming, 'FarmEntered').withArgs(tokenId, incentiveId, liquidity); + }); + + it('sets the farm struct properly', async () => { + const liquidity = (await context.nft.positions(tokenId)).liquidity; + + const farmBefore = await context.eternalFarming.farms(tokenId, incentiveId); + await subject(tokenId, lpUser0); + const farmAfter = await context.eternalFarming.farms(tokenId, incentiveId); + + expect(farmBefore.liquidity).to.eq(0); + expect(farmAfter.liquidity).to.eq(liquidity); + }); + + it('has gas cost [ @skip-on-coverage ]', async () => await snapshotGasCost(subject(tokenId, lpUser0))); + }); + + describe('fails when', () => { + it('deposit is already farmd in the incentive', async () => { + await subject(tokenId, lpUser0); + await expect(subject(tokenId, lpUser0)).to.be.revertedWith('Token already farmed'); + }); + + it('trying to forcefully enter twice', async () => { + await subject(tokenId, lpUser0); + await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(lpUser0); + + await expect( + context.eternalFarming.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ) + ).to.be.revertedWithCustomError(context.eternalFarming, 'tokenAlreadyFarmed'); + }); + + it('farming deactivated', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }); + await expect(subject(tokenId, lpUser0)).to.be.revertedWithCustomError(context.eternalFarming, 'incentiveStopped'); + }); + + it('farming indirectly deactivated', async () => { + await detachIncentiveIndirectly(localNonce); + + await expect(subject(tokenId, lpUser0)).to.be.revertedWithCustomError(context.eternalFarming, 'incentiveStopped'); + }); + + it('you are not the owner of the deposit', async () => { + // lpUser2 calls, we're using lpUser0 elsewhere. + await expect(subject(tokenId, actors.lpUser2())).to.be.revertedWith('Not approved for token'); + }); + + it('has 0 liquidity in the position', async () => { + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + const tokenId2 = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await context.nft.connect(lpUser0).approveForFarming(tokenId2, true, context.farmingCenter); + + await context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenId2, + liquidity: (await context.nft.positions(tokenId2)).liquidity, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1_000, + }); + await expect(subject(tokenId2, lpUser0)).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'zeroLiquidity'); + }); + + it('token id is for a different pool than the incentive', async () => { + const incentive2 = await helpers.createIncentiveFlow({ + ...incentiveArgs, + poolAddress: context.pool12, + }); + const { tokenId: otherTokenId } = await helpers.mintFlow({ + lp: lpUser0, + tokens: [context.token1, context.rewardToken], + }); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId: otherTokenId, + }); + + await expect( + context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + otherTokenId + ) + ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'invalidPool'); + }); + + it('incentive key does not exist', async () => { + const _nonce = BN(999); + await expect( + context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: _nonce, + }, + tokenId + ) + ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'incentiveNotExist'); + }); + }); + }); + + describe('#getRewardInfo', () => { + let incentiveId: string; + let farmIncentiveKey: ContractParams.IncentiveKey; + + beforeEach('set up incentive and farm', async () => { + timestamps = makeTimestamps((await blockTimestamp()) + 1_000); + + const mintResult = await helpers.mintFlow({ + lp: lpUser0, + tokens: [context.token0, context.token1], + }); + tokenId = mintResult.tokenId; + + farmIncentiveKey = { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: 0, + }; + + incentiveId = await helpers.getIncentiveId( + await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: 0n, + rewardRate: 10n, + bonusRewardRate: 50n, + }) + ); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + await context.farmingCenter.connect(lpUser0).enterFarming(farmIncentiveKey, tokenId); + await context.eternalFarming.farms(tokenId, incentiveId); + }); + + it('returns correct rewardAmount and secondsInsideX128 for the position', async () => { + const pool = context.poolObj.connect(actors.lpUser0()); + + await Time.set(timestamps.startTime + 10); + //await provider.send('evm_mine', [timestamps.startTime + 100]) + const trader = actors.traderUser0(); + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 10, + }); + + await Time.set(timestamps.endTime - 10); + + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 100, + }); + + await Time.set(timestamps.endTime + 10); + + const rewardInfo = await context.eternalFarming.connect(lpUser0).getRewardInfo(farmIncentiveKey, tokenId); + + const { tickLower, tickUpper } = await context.nft.positions(tokenId); + const farm = await context.eternalFarming.farms(tokenId, incentiveId); + + // @ts-ignore + expect(rewardInfo.reward).to.be.closeTo(BN('9900'), BN('10000')); + //expect(rewardInfo.secondsInsideX128).to.equal(expectedSecondsInPeriod) + }); + + it('reverts if farm does not exist', async () => { + // await Time.setAndMine(timestamps.endTime + 1) + + await expect(context.eternalFarming.connect(lpUser0).getRewardInfo(farmIncentiveKey, '100')).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'farmDoesNotExist' + ); + }); + }); + + describe('#decreaseRewards', () => { + let incentiveArgs: HelperTypes.CreateIncentive.Args; + let incentiveKey: ContractParams.IncentiveKey; + let virtualPool: EternalVirtualPool; + + let factoryOwner: Wallet; + + beforeEach('set up incentive and farm', async () => { + factoryOwner = actors.wallets[0]; + incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: context.pool01, + nonce: 0n, + rewardRate: 10000n, + bonusRewardRate: 50000n, + }; + + incentiveKey = { + nonce: 0n, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + + pool: context.pool01, + }; + + const vpFactory = await ethers.getContractFactory('EternalVirtualPool'); + const createIncentiveResult = await helpers.createIncentiveFlow(incentiveArgs); + const _vpool = createIncentiveResult.virtualPool; + virtualPool = vpFactory.attach(_vpool) as any as EternalVirtualPool; + }); + + it('onlyOwner', async () => { + expect(context.eternalFarming.connect(lpUser0).decreaseRewardsAmount(incentiveKey, 100, 100)).to.be.revertedWithoutReason; + }); + + it('can decrease rewards before start', async () => { + await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 100, 100)) + .to.emit(context.rewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) + .to.emit(context.bonusRewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) + .to.emit(context.eternalFarming, 'RewardAmountsDecreased'); + + const reserves = await virtualPool.rewardReserves(); + + expect(reserves[0]).to.be.eq(totalReward - 100n); + expect(reserves[1]).to.be.eq(bonusReward - 100n); + }); + + it('can decrease only main reward', async () => { + await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 100, 0)) + .to.emit(context.eternalFarming, 'RewardAmountsDecreased') + .to.emit(context.rewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) + .to.not.emit(context.bonusRewardToken, 'Transfer'); + + const reserves = await virtualPool.rewardReserves(); + + expect(reserves[0]).to.be.eq(totalReward - 100n); + expect(reserves[1]).to.be.eq(bonusReward); + }); + + it('can decrease only bonus reward', async () => { + await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 0, 100)) + .to.emit(context.eternalFarming, 'RewardAmountsDecreased') + .to.emit(context.bonusRewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, 100) + .to.not.emit(context.rewardToken, 'Transfer'); + + const reserves = await virtualPool.rewardReserves(); + + expect(reserves[0]).to.be.eq(totalReward); + expect(reserves[1]).to.be.eq(bonusReward - 100n); + }); + + it('cannot exceed reserves', async () => { + await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, totalReward + 1n, bonusReward + 1n)) + .to.emit(context.rewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, totalReward - 1n) + .to.emit(context.bonusRewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, bonusReward) + .to.emit(context.eternalFarming, 'RewardAmountsDecreased'); + }); + + it('max uint128', async () => { + await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 2n ** 128n - 1n, 2n ** 128n - 1n)) + .to.emit(context.rewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, totalReward - 1n) + .to.emit(context.bonusRewardToken, 'Transfer') + .withArgs(await context.eternalFarming.getAddress(), factoryOwner.address, bonusReward) + .to.emit(context.eternalFarming, 'RewardAmountsDecreased'); + }); + + it('decrease with 0 amount', async () => { + await expect(context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 0, 0)); + }); + }); + + describe('#deactivate incentive', () => { + let incentiveArgs: HelperTypes.CreateIncentive.Args; + let incentiveKey: ContractParams.IncentiveKey; + let virtualPool: EternalVirtualPool; + let virtualPoolAddress: string; + + let localNonce = 0n; + + let factoryOwner: Wallet; + + beforeEach('set up incentive and farm', async () => { + localNonce = await context.eternalFarming.numOfIncentives(); + factoryOwner = actors.wallets[0]; + incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: context.pool01, + nonce: localNonce, + rewardRate: 10000n, + bonusRewardRate: 50000n, + }; + + incentiveKey = { + nonce: localNonce, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + + pool: context.pool01, + }; + + const vpFactory = await ethers.getContractFactory('EternalVirtualPool'); + const createIncentiveResult = await helpers.createIncentiveFlow(incentiveArgs); + const _vpool = createIncentiveResult.virtualPool; + virtualPool = vpFactory.attach(_vpool) as any as EternalVirtualPool; + virtualPoolAddress = await virtualPool.getAddress(); + }); + + it('deactivate incentive', async () => { + let activeIncentiveBefore = await context.pluginObj.incentive(); + + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + let activeIncentiveAfter = await context.pluginObj.incentive(); + + expect(activeIncentiveBefore).to.equal(await virtualPool.getAddress()); + expect(activeIncentiveAfter).to.equal(ZERO_ADDRESS); + + const rewardRates = await virtualPool.rewardRates(); + + expect(rewardRates[0]).to.be.eq(0); + expect(rewardRates[1]).to.be.eq(0); + }); + + it('deactivate incentive with zero rates', async () => { + let activeIncentiveBefore = await context.pluginObj.incentive(); + + await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 0, 0); + + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + let activeIncentiveAfter = await context.pluginObj.incentive(); + + expect(activeIncentiveBefore).to.equal(virtualPoolAddress); + expect(activeIncentiveAfter).to.equal(ZERO_ADDRESS); + + const rewardRates = await virtualPool.rewardRates(); + + expect(rewardRates[0]).to.be.eq(0); + expect(rewardRates[1]).to.be.eq(0); + }); + + it('deactivate incentive only incentiveMaker', async () => { + let activeIncentiveBefore = await context.pluginObj.incentive(); + + expect(context.eternalFarming.connect(lpUser0).deactivateIncentive(incentiveKey)).to.be.revertedWithoutReason; + let activeIncentiveAfter = await context.pluginObj.incentive(); + + expect(activeIncentiveBefore).to.equal(virtualPoolAddress); + expect(activeIncentiveAfter).to.equal(virtualPoolAddress); + }); + + it('cannot deactivate twice', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.be.revertedWithCustomError( + context.eternalFarming, + 'incentiveStopped' + ); + }); + + it('cannot deactivate nonexistent incentive', async () => { + const invalidKey = { ...incentiveKey }; + invalidKey.nonce = 999; + await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(invalidKey)).to.be.revertedWithCustomError( + context.eternalFarming, + 'incentiveNotExist' + ); + }); + + it('can deactivate manually after indirect deactivation', async () => { + await detachIncentiveIndirectly(localNonce); + await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.not.be.reverted; + }); + + it('can deactivate manually after indirect deactivation and exit', async () => { + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + const tokenIdNarrow = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: 120, + tickUpper: 180, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId: tokenIdNarrow, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenIdNarrow + ); + + const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); + const virtualPoolFactory = await ethers.getContractFactory('EternalVirtualPool'); + const virtualPool = virtualPoolFactory.attach(incentiveAddress) as any as EternalVirtualPool; + + expect(await virtualPool.deactivated()).to.be.false; + const incentiveId = await helpers.getIncentiveId({ + poolAddress: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + virtualPool: virtualPool as any as Contract, + nonce: localNonce, + bonusReward: 0n, + totalReward: 0n, + }); + expect((await context.eternalFarming.incentives(incentiveId)).deactivated).to.be.false; + + await detachIncentiveIndirectly(localNonce); + + await context.farmingCenter.connect(lpUser0).exitFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenIdNarrow + ); + + expect(await virtualPool.deactivated()).to.be.true; + expect((await context.eternalFarming.incentives(incentiveId)).deactivated).to.be.false; + + await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.not.be.reverted; + + expect(await virtualPool.deactivated()).to.be.true; + expect((await context.eternalFarming.incentives(incentiveId)).deactivated).to.be.true; + }); + + it('can deactivate manually if farming detached manually from plugin', async () => { + await context.pluginFactory.setFarmingAddress(incentiveCreator.address); + await context.pluginObj.connect(incentiveCreator).setIncentive(ZERO_ADDRESS); + + await expect(context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey)).to.not.be.reverted; + }); + + it('cross lower after deactivate', async () => { + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 2n, await context.nft.getAddress()); + + await erc20Helper.ensureBalancesAndApprovals( + incentiveCreator, + [context.rewardToken, context.bonusRewardToken], + BNe18(1), + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(incentiveCreator).addRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce: 0, + }, + BNe18(1), + BNe18(1) + ); + + const tokenIdWide = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + const tokenIdNarrow = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: 120, + tickUpper: 180, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId: tokenIdWide, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: 0, + }, + tokenIdWide + ); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId: tokenIdNarrow, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: 0, + }, + tokenIdNarrow + ); + + await helpers.moveTickTo({ direction: 'up', desiredValue: 150, trader: actors.farmingDeployer() }); + + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + + await helpers.moveTickTo({ direction: 'down', desiredValue: -200, trader: actors.farmingDeployer() }); + + await context.farmingCenter.connect(lpUser0).collectRewards(incentiveKey, tokenIdNarrow); + let rewards = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusRewards = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + let vpTick = await virtualPool.globalTick(); + expect(rewards).to.eq(9970); + expect(bonusRewards).to.eq(49851); + expect(vpTick).to.eq(150); + }); + + it('cross upper after deactivate', async () => { + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 2n, await context.nft.getAddress()); + + await erc20Helper.ensureBalancesAndApprovals( + incentiveCreator, + [context.rewardToken, context.bonusRewardToken], + BNe18(1), + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(incentiveCreator).addRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce: 0, + }, + BNe18(1), + BNe18(1) + ); + + const tokenIdWide = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + const tokenIdNarrow = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: -180, + tickUpper: -120, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId: tokenIdWide, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: 0, + }, + tokenIdWide + ); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId: tokenIdNarrow, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: 0, + }, + tokenIdNarrow + ); + + await helpers.moveTickTo({ direction: 'down', desiredValue: -150, trader: actors.farmingDeployer() }); + + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + + await helpers.moveTickTo({ direction: 'up', desiredValue: 200, trader: actors.farmingDeployer() }); + + await context.farmingCenter.connect(lpUser0).collectRewards(incentiveKey, tokenIdNarrow); + let rewards = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusRewards = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + let vpTick = await virtualPool.globalTick(); + expect(rewards).to.eq(9970); + expect(bonusRewards).to.eq(49851); + expect(vpTick).to.eq(-150); + }); + }); + + describe('#claimReward', () => { + let createIncentiveResult: HelperTypes.CreateIncentive.Result; + let subject: (token: string | TestERC20, to: string, amount: bigint) => Promise; + // The amount the user should be able to claim + let claimable: bigint; + let localNonce = 0n; + + beforeEach('setup', async () => { + timestamps = makeTimestamps(await blockTimestamp()); + const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); + + localNonce = await context.eternalFarming.numOfIncentives(); + + createIncentiveResult = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + + await Time.setAndMine(timestamps.startTime + 100); + + const mintResult = await helpers.mintDepositFarmFlow({ + lp: lpUser0, + tokensToFarm, + ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], + amountsToFarm: [amountDesired, amountDesired], + createIncentiveResult, + }); + tokenId = mintResult.tokenId; + + await Time.setAndMine(timestamps.endTime + 10); + await context.farmingCenter.connect(lpUser0).exitFarming( + { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: localNonce, + }, + tokenId + ); + + claimable = await context.eternalFarming.rewards(lpUser0.address, await context.rewardToken.getAddress()); + + subject = (_token: string | TestERC20, _to: string, _amount: bigint) => + context.eternalFarming.connect(lpUser0).claimReward(_token, _to, _amount); + }); + + describe('when requesting the full amount', () => { + it('emits RewardClaimed event', async () => { + const { rewardToken } = context; + claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); + await expect(subject(rewardToken, lpUser0.address, BN('0'))) + .to.emit(context.eternalFarming, 'RewardClaimed') + .withArgs(lpUser0.address, claimable, await context.rewardToken.getAddress(), lpUser0.address); + }); + + it('transfers the correct reward amount to destination address', async () => { + const { rewardToken } = context; + claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); + const balance = await rewardToken.balanceOf(lpUser0.address); + await subject(rewardToken, lpUser0.address, BN('0')); + expect(await rewardToken.balanceOf(lpUser0.address)).to.equal(balance + claimable); + }); + + it('sets the claimed reward amount to zero', async () => { + const { rewardToken } = context; + expect(await context.eternalFarming.rewards(lpUser0.address, rewardToken)).to.not.equal(0); + + await subject(rewardToken, lpUser0.address, BN('0')); + + expect(await context.eternalFarming.rewards(lpUser0.address, rewardToken)).to.equal(0); + }); + + it('has gas cost [ @skip-on-coverage ]', async () => + await snapshotGasCost(subject(await context.rewardToken.getAddress(), lpUser0.address, BN('0')))); + + it('returns their claimable amount', async () => { + const { rewardToken, eternalFarming } = context; + const amountBefore = await rewardToken.balanceOf(lpUser0.address); + await subject(rewardToken, lpUser0.address, BN('0')); + expect(await eternalFarming.rewards(lpUser0.address, rewardToken)).to.eq(BN('0')); + expect(await rewardToken.balanceOf(lpUser0.address)).to.eq(amountBefore + claimable); + }); + }); + + describe('when requesting a nonzero amount', () => { + it('emits RewardClaimed event', async () => { + const { rewardToken } = context; + await expect(subject(rewardToken, lpUser0.address, claimable)) + .to.emit(context.eternalFarming, 'RewardClaimed') + .withArgs(lpUser0.address, claimable, await context.rewardToken.getAddress(), lpUser0.address); + }); + + it('transfers the correct reward amount to destination address', async () => { + const { rewardToken } = context; + claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); + const balance = await rewardToken.balanceOf(lpUser0.address); + await subject(rewardToken, lpUser0.address, claimable); + expect(await rewardToken.balanceOf(lpUser0.address)).to.equal(balance + claimable); + }); + + it('reverts if transfer to zero', async () => { + const { rewardToken } = context; + claimable = await context.eternalFarming.rewards(lpUser0.address, rewardToken); + await expect(subject(rewardToken, ZERO_ADDRESS, claimable)).to.be.revertedWithCustomError(context.eternalFarming, 'claimToZeroAddress'); + }); + + it('sets the claimed reward amount to the correct amount', async () => { + const { rewardToken, eternalFarming } = context; + const initialRewardBalance = await eternalFarming.rewards(lpUser0.address, rewardToken); + expect(initialRewardBalance).to.not.equal(BN('0')); + + const partialClaim = initialRewardBalance / BN('3'); + await subject(rewardToken, lpUser0.address, partialClaim); + + expect(await eternalFarming.rewards(lpUser0.address, rewardToken)).to.eq(initialRewardBalance - partialClaim); + }); + + it('not emit event if nothing to claim', async () => { + const { rewardToken } = context; + await expect(context.eternalFarming.connect(actors.lpUser2()).claimReward(rewardToken, actors.lpUser2().address, 100n)).to.not.emit( + context.eternalFarming, + 'RewardClaimed' + ); + }); + + describe('when user claims more than they have', () => { + it('only transfers what they have', async () => { + const { rewardToken, eternalFarming } = context; + const amountBefore = await rewardToken.balanceOf(lpUser0.address); + await subject(rewardToken, lpUser0.address, claimable * 3n); + expect(await eternalFarming.rewards(lpUser0.address, rewardToken)).to.eq(BN('0')); + expect(await rewardToken.balanceOf(lpUser0.address)).to.eq(amountBefore + claimable); + }); + }); + }); + }); + + describe('#collectRewards', async () => { + let createIncentiveResult: HelperTypes.CreateIncentive.Result; + let localNonce = 0n; + + beforeEach('setup', async () => { + timestamps = makeTimestamps(await blockTimestamp()); + const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); + + localNonce = await context.eternalFarming.numOfIncentives(); + + createIncentiveResult = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + + await Time.setAndMine(timestamps.startTime + 100); + + const mintResult = await helpers.mintDepositFarmFlow({ + lp: lpUser0, + tokensToFarm, + ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], + amountsToFarm: [amountDesired, amountDesired], + createIncentiveResult, + }); + tokenId = mintResult.tokenId; + + await Time.setAndMine(timestamps.endTime + 10); + + await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(lpUser0); + }); + + it('cannot collect from nonexistent farm', async () => { + const invalidTokenId = 9999n; + await expect( + context.eternalFarming.connect(lpUser0).collectRewards( + { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: localNonce, + }, + invalidTokenId, + lpUser0.address + ) + ).to.be.revertedWithCustomError(context.eternalFarming, 'farmDoesNotExist'); + }); + + it('do not update rewards if nothing to collect', async () => { + let rewardTokenAddress = await context.rewardToken.getAddress() + let bonusRewardTokenAddress = await context.bonusRewardToken.getAddress() + + await context.eternalFarming.connect(actors.wallets[0]).setRates( + { + rewardToken: rewardTokenAddress, + bonusRewardToken: bonusRewardTokenAddress, + pool: context.pool01, + nonce: localNonce, + }, + 0, + 0 + ); + + await context.eternalFarming.connect(lpUser0).collectRewards( + { + rewardToken: rewardTokenAddress, + bonusRewardToken: bonusRewardTokenAddress, + pool: context.pool01, + nonce: localNonce, + }, + tokenId, + lpUser0.address + ); + + const rewardTokenBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, rewardTokenAddress); + const bonusRewardTokenBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, bonusRewardTokenAddress); + + await context.eternalFarming.connect(lpUser0).collectRewards( + { + rewardToken: rewardTokenAddress, + bonusRewardToken: bonusRewardTokenAddress, + pool: context.pool01, + nonce: localNonce, + }, + tokenId, + lpUser0.address + ); + + const rewardTokenBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, rewardTokenAddress); + const bonusRewardTokenBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, bonusRewardTokenAddress); + + + expect(rewardTokenBalanceAfter).to.be.eq(rewardTokenBalanceBefore); + expect(bonusRewardTokenBalanceAfter).to.be.eq(bonusRewardTokenBalanceBefore); + }); + }); + + describe('#exitFarming', () => { + let incentiveId: string; + let subject: (actor: Wallet) => Promise; + let createIncentiveResult: HelperTypes.CreateIncentive.Result; + + let localNonce = 0n; + + describe('before end time', () => { + it('can exitFarming', async () => { + timestamps = makeTimestamps(await blockTimestamp()); + + localNonce = await context.eternalFarming.numOfIncentives(); + + createIncentiveResult = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: context.token0, + token1: context.token1, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + // await Time.setAndMine(timestamps.startTime + 1) + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: localNonce, + }, + tokenId + ); + + incentiveId = await helpers.getIncentiveId(createIncentiveResult); + + await expect( + context.farmingCenter.connect(actors.lpUser0()).exitFarming( + { + pool: context.pool01, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + nonce: localNonce, + }, + tokenId + ) + ).to.be.emit(context.eternalFarming, 'FarmEnded'); + }); + }); + + describe('after end time', () => { + let tokenIdOut: string; + beforeEach('create the incentive and nft and farm it', async () => { + timestamps = makeTimestamps(await blockTimestamp()); + + localNonce = await context.eternalFarming.numOfIncentives(); + + createIncentiveResult = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 3n, await context.nft.getAddress()); + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: context.token0, + token1: context.token1, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + tokenIdOut = await mintPosition(context.nft.connect(lpUser0), { + token0: context.token0, + token1: context.token1, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]) + TICK_SPACINGS[FeeAmount.MEDIUM], + recipient: lpUser0.address, + amount0Desired: 0, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 10000, + }); + + await Time.setAndMine(timestamps.startTime + 100); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + await context.nft.connect(lpUser0).approveForFarming(tokenIdOut, true, context.farmingCenter); + await context.farmingCenter.connect(lpUser0).enterFarming( + { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: localNonce, + }, + tokenId + ); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: localNonce, + }, + tokenIdOut + ); + + await Time.setAndMine(timestamps.endTime + 10); + + incentiveId = await helpers.getIncentiveId(createIncentiveResult); + subject = (_actor: Wallet) => + context.farmingCenter.connect(_actor).exitFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + }); + + describe('works and', () => { + it('emits an exitFarmingd event', async () => { + await expect(subject(lpUser0)) + .to.emit(context.eternalFarming, 'FarmEnded') + .withArgs( + tokenId, + incentiveId, + await context.rewardToken.getAddress(), + await context.bonusRewardToken.getAddress(), + lpUser0.address, + 9079n, + 199n + ); + }); + + it('has gas cost [ @skip-on-coverage ]', async () => { + await snapshotGasCost(subject(lpUser0)); + }); + + it('updates the reward available for the context.tokenomics', async () => { + const rewardsAccured = await context.eternalFarming.rewards(lpUser0.address, await context.rewardToken.getAddress()); + await subject(lpUser0); + expect(await context.eternalFarming.rewards(lpUser0.address, await context.rewardToken.getAddress())).to.be.gt(rewardsAccured); + }); + + it('updates the farm struct', async () => { + const farmBefore = await context.eternalFarming.farms(tokenId, incentiveId); + await subject(lpUser0); + const farmAfter = await context.eternalFarming.farms(tokenId, incentiveId); + + expect(farmBefore.liquidity).to.gt(0); + expect(farmAfter.liquidity).to.eq(0); + }); + }); + + it('can exit without rewards', async () => { + await expect( + context.farmingCenter.connect(lpUser0).exitFarming( + { + pool: context.pool01, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + nonce: localNonce, + }, + tokenIdOut + ) + ) + .to.emit(context.eternalFarming, 'FarmEnded') + .withArgs( + tokenIdOut, + incentiveId, + await context.rewardToken.getAddress(), + await context.bonusRewardToken.getAddress(), + lpUser0.address, + BN('0'), + BN('0') + ); + }); + + it('cannot exit twice', async () => { + await subject(lpUser0); + await expect( + context.farmingCenter.connect(lpUser0).exitFarming( + { + pool: context.pool01, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + nonce: localNonce, + }, + tokenId + ) + ).to.be.revertedWith('Invalid incentiveId'); + }); + + it('cannot exit from nonexistent farming', async () => { + await context.eternalFarming.connect(actors.wallets[0]).setFarmingCenterAddress(lpUser0); + + await expect( + context.eternalFarming.connect(lpUser0).exitFarming( + { + pool: context.pool12, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + nonce: localNonce, + }, + tokenId, + lpUser0.address + ) + ).to.be.revertedWithCustomError(context.eternalFarming, 'farmDoesNotExist'); + }); + + it('can exit from deactivated farming', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }); + await subject(lpUser0); + }); + + it('can exit from indirectly deactivated farming', async () => { + await detachIncentiveIndirectly(localNonce); + await subject(lpUser0); + }); + + //it('calculates the right secondsPerLiquidity') + //it('does not overflow totalSecondsUnclaimed') + }); + + it('rewards calculation underflow', async () => { + const helpers = HelperCommands.fromTestContext(context, actors, provider); + + const localNonce = await context.eternalFarming.numOfIncentives(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired * 3n, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + const incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward: 1000000n, + bonusReward: 1000000n, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }; + + const incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + + const trader = actors.traderUser0(); + + await helpers.makeTickGoFlow({ + trader, + direction: 'down', + desiredValue: -30, + }); + + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 0, + }); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: -120, + tickUpper: -60, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + + await helpers.makeTickGoFlow({ + trader, + direction: 'down', + desiredValue: -150, + }); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: -240, + tickUpper: -60, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + + let time = await blockTimestamp(); + + await Time.set(time + 10000); + + await helpers.makeTickGoFlow({ + trader, + direction: 'down', + desiredValue: -238, + }); + + await context.farmingCenter.connect(lpUser0).exitFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: localNonce, + }, + tokenId + ); + }); + + describe('fails if', () => { + it('farm has already been exitFarming', async () => { + await expect(subject(lpUser0)).to.revertedWith('ERC721: invalid token ID'); + }); + + it('if reentrancy lock in pool is locked', async () => { + const _factory = await ethers.getContractFactory('TestERC20Reentrant'); + const tokenReentrant = (await _factory.deploy(MaxUint256 / 2n)) as any as TestERC20Reentrant; + + const [token0, token1] = + (await tokenReentrant.getAddress()) < (await context.token1.getAddress()) + ? [tokenReentrant, context.token1] + : [context.token1, tokenReentrant]; + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [token0, token1], amountDesired, await context.nft.getAddress()); + + await context.nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); + + const poolAddress = await context.factory.poolByPair(token0, token1); + + const _tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: token0, + token1: token1, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + const _nonce = await context.eternalFarming.numOfIncentives(); + + await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress, + nonce: _nonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + + await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: poolAddress, + nonce: _nonce, + }, + _tokenId + ); + + //await tokenReentrant.prepareAttack(incentiveKey, 500, 500); + + await context.nft.connect(lpUser0).approve(tokenReentrant, _tokenId); + const txData = await context.farmingCenter.exitFarming.populateTransaction( + { + pool: poolAddress, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: _nonce, + }, + _tokenId + ); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [token0, token1], amountDesired, await context.router.getAddress()); + const swapData = { + tokenIn: tokenReentrant, + tokenOut: context.token1, + deployer: ZERO_ADDRESS, + amountIn: 10, + amountOutMinimum: 0, + recipient: lpUser0.address, + deadline: (await blockTimestamp()) + 10000, + limitSqrtPrice: 0, + }; + + await tokenReentrant.prepareComplexAttack(context.farmingCenter, txData.data); + await expect(context.router.connect(lpUser0).exactInputSingle(swapData)).to.be.revertedWith('STF'); + + await tokenReentrant.cancelComplexAttack(); + await expect(context.router.connect(lpUser0).exactInputSingle(swapData)).to.be.not.reverted; + }); + }); + }); + + describe('liquidityIfOverflow', () => { + const MAX_UINT_96 = 2n ** 96n - 1n; + + let incentive: HelperTypes.CreateIncentive.Result; + let incentiveId: string; + let localNonce = 0n; + + beforeEach(async () => { + timestamps = makeTimestamps(1_000 + (await blockTimestamp())); + localNonce = await context.eternalFarming.numOfIncentives(); + incentive = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + incentiveId = await helpers.getIncentiveId(incentive); + // await Time.setAndMine(timestamps.startTime + 1) + }); + + it('works when no overflow', async () => { + // With this `amount`, liquidity ends up less than MAX_UINT96 + const amount = MAX_UINT_96 / 1000n; + + const { tokenId } = await helpers.mintFlow({ + lp: lpUser0, + tokens: [context.token0, context.token1], + amounts: [amount, amount], + tickLower: 0, + tickUpper: 10 * TICK_SPACINGS[FeeAmount.MEDIUM], + }); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming(await incentiveResultToFarmAdapter(incentive), tokenId); + const farm = await context.eternalFarming.farms(tokenId, incentiveId); + expect(farm.liquidity).to.be.lt(MAX_UINT_96); + }); + + it('works when overflow', async () => { + // With this `amount`, liquidity ends up more than MAX_UINT96 + const amount = MAX_UINT_96 - 100n; + const { tokenId } = await helpers.mintFlow({ + lp: lpUser0, + tokens: [context.token0, context.token1], + amounts: [amount, amount], + tickLower: 0, + tickUpper: 10 * TICK_SPACINGS[FeeAmount.MEDIUM], + }); + + await helpers.depositFlow({ + lp: lpUser0, + tokenId, + }); + + await context.farmingCenter.connect(lpUser0).enterFarming(await incentiveResultToFarmAdapter(incentive), tokenId); + const farm = await context.eternalFarming.farms(tokenId, incentiveId); + expect(farm.liquidity).to.be.gt(MAX_UINT_96); + }); + }); + + describe('#rewards', async () => { + let incentiveArgs: HelperTypes.CreateIncentive.Args; + let incentiveKey: ContractParams.IncentiveKey; + let incentiveId: string; + + let localNonce = 0n; + + beforeEach(async () => { + /** We will be doing a lot of time-testing here, so leave some room between + and when the incentive starts */ + bonusReward = BN(10000); + timestamps = makeTimestamps(1_000 + (await blockTimestamp())); + + localNonce = await context.eternalFarming.numOfIncentives(); + + incentiveArgs = { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce: localNonce, + rewardRate: 100n, + bonusRewardRate: 3n, + }; + + incentiveKey = { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool01, + nonce: localNonce, + }; + + incentiveId = await helpers.getIncentiveId(await helpers.createIncentiveFlow(incentiveArgs)); + }); + + describe('#addRewards', async () => { + it('can add rewards', async () => { + let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + await erc20Helper.ensureBalancesAndApprovals( + lpUser0, + [context.rewardToken, context.bonusRewardToken], + amountDesired, + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired); + + let incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + expect(incentiveAfter.totalReward - amountDesired).to.eq(incentiveBefore.totalReward); + expect(incentiveAfter.bonusReward - amountDesired).to.eq(incentiveBefore.bonusReward); + }); + + it('can add rewards in deflationary token', async () => { + await context.rewardToken.setDefl(true, 5); + + let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + await erc20Helper.ensureBalancesAndApprovals( + lpUser0, + [context.rewardToken, context.bonusRewardToken], + amountDesired * 2n, + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired); + + let incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + expect(incentiveAfter.totalReward).to.be.gt(incentiveBefore.totalReward); + expect(incentiveAfter.bonusReward - amountDesired).to.eq(incentiveBefore.bonusReward); + }); + + it('cannot add rewards if token does incorrect transfer', async () => { + await erc20Helper.ensureBalancesAndApprovals( + lpUser0, + [context.rewardToken, context.bonusRewardToken], + 2n ** 128n + 10n, + await context.eternalFarming.getAddress() + ); + + await context.rewardToken.setDefl(true, 100); + + expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired)).to.be.revertedWithoutReason; + + await context.rewardToken.setDefl(false, 0); + + await context.rewardToken.setNextTransferAmount(2n ** 128n); + + await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, amountDesired, amountDesired)).to.be.revertedWithCustomError( + context.eternalFarming, + 'invalidTokenAmount' + ); + }); + + it('can add rewards with 0 amounts', async () => { + let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + await erc20Helper.ensureBalancesAndApprovals( + lpUser0, + [context.rewardToken, context.bonusRewardToken], + amountDesired, + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 0, 0); + + let incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + expect(incentiveAfter.totalReward).to.eq(incentiveBefore.totalReward); + expect(incentiveAfter.bonusReward).to.eq(incentiveBefore.bonusReward); + + await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 0, 1); + incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + expect(incentiveAfter.totalReward).to.eq(incentiveBefore.totalReward); + expect(incentiveAfter.bonusReward).to.eq(incentiveBefore.bonusReward + 1n); + + await context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 1, 0); + incentiveAfter = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + expect(incentiveAfter.totalReward).to.eq(incentiveBefore.totalReward + 1n); + expect(incentiveAfter.bonusReward).to.eq(incentiveBefore.bonusReward + 1n); + }); + + it('can add rewards with uint128 amounts', async () => { + let incentiveBefore = await context.eternalFarming.connect(lpUser0).incentives(incentiveId); + + await erc20Helper.ensureBalancesAndApprovals( + lpUser0, + [context.rewardToken, context.bonusRewardToken], + 2n ** 128n, + await context.eternalFarming.getAddress() + ); + + let factoryOwner = actors.wallets[0]; + + await context.eternalFarming.connect(factoryOwner).decreaseRewardsAmount(incentiveKey, 10000n, 10000n); + await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 2n ** 128n - 1n, 2n ** 128n - 1n)).to.be.reverted; + }); + + it('cannot add rewards to non-existent incentive', async () => { + incentiveKey = { + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool12, + nonce: localNonce, + }; + + await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 0, 0)).to.be.revertedWithCustomError( + context.eternalFarming as AlgebraEternalFarming, + 'incentiveNotExist' + ); + }); + + it('cannot add rewards to deactivated incentive', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + + await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 1, 1)).to.be.revertedWithCustomError( + context.eternalFarming, + 'incentiveStopped' + ); + }); + + it('addRewards to indirectly deactivated incentive', async () => { + await detachIncentiveIndirectly(localNonce); + + await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey, 1, 1)).to.be.revertedWithCustomError( + context.eternalFarming, + 'incentiveStopped' + ); + }); + + it('cannot reenter to addRewards', async () => { + const _factory = await ethers.getContractFactory('TestERC20Reentrant'); + const tokenReentrant = (await _factory.deploy(MaxUint256 / 2n)) as any as TestERC20Reentrant; + + await erc20Helper.ensureBalancesAndApprovals( + lpUser0, + [tokenReentrant, context.bonusRewardToken], + amountDesired, + await context.eternalFarming.getAddress() + ); + + const nonce = await context.eternalFarming.numOfIncentives(); + await helpers.createIncentiveFlow({ + rewardToken: tokenReentrant, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: context.pool12, + nonce: nonce, + rewardRate: 10n, + bonusRewardRate: 50n, + }); + + const incentiveKey2 = { + rewardToken: await tokenReentrant.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + pool: context.pool12, + nonce: nonce, + }; + + await tokenReentrant.prepareAttack(incentiveKey, 500, 500); + + await expect(context.eternalFarming.connect(lpUser0).addRewards(incentiveKey2, 1, 1)).to.be.revertedWith('STF'); + }); + }); + + describe('#setRates', async () => { + it('can change rates', async () => { + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: context.token0, + token1: context.token1, + fee: FeeAmount.MEDIUM, + tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await context.nft.connect(lpUser0).approveForFarming(tokenId, true, context.farmingCenter); + + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: await context.rewardToken.getAddress(), + bonusRewardToken: await context.bonusRewardToken.getAddress(), + nonce: localNonce, + }, + tokenId + ); + await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, BN(60), BN(5)); + let rewardsBefore = await context.eternalFarming.getRewardInfo(incentiveKey, tokenId); + let time = await blockTimestamp(); + + await Time.set(time + 100); + + const trader = actors.traderUser0(); + + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 10, + }); + time = await blockTimestamp(); + let rewardsAfter = await context.eternalFarming.getRewardInfo(incentiveKey, tokenId); + + expect(rewardsAfter.reward - rewardsBefore.reward).to.eq(60n * 104n); + expect(rewardsAfter.bonusReward - rewardsBefore.bonusReward).to.eq(5n * 104n); + }); + + it('cannot set nonzero to deactivated incentive', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + + await expect(context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 1, 1)).to.be.revertedWithCustomError( + context.eternalFarming, + 'incentiveStopped' + ); + }); + + it('set zero to deactivated incentive', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive(incentiveKey); + await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 0, 0); + }); + + it('set max rates', async () => { + await expect(context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 2n ** 128n - 1n, 2n ** 128n - 1n)) + .to.emit(context.eternalFarming, 'RewardsRatesChanged') + .withArgs(2n ** 128n - 1n, 2n ** 128n - 1n, incentiveId); + }); + + it('cannot set nonzero to indirectly deactivated incentive', async () => { + await detachIncentiveIndirectly(localNonce); + + await expect(context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 1, 1)).to.be.revertedWithCustomError( + context.eternalFarming, + 'incentiveStopped' + ); + }); + + it('set zero to indirectly deactivated incentive', async () => { + await detachIncentiveIndirectly(localNonce); + + await context.eternalFarming.connect(incentiveCreator).setRates(incentiveKey, 0, 0); + }); + }); + }); +}); diff --git a/src/farming/test/unit/FarmingCenter.spec.ts b/src/farming/test/unit/FarmingCenter.spec.ts index c1542518a..7d6708083 100644 --- a/src/farming/test/unit/FarmingCenter.spec.ts +++ b/src/farming/test/unit/FarmingCenter.spec.ts @@ -1,775 +1,775 @@ -import { ethers } from 'hardhat'; -import { Wallet } from 'ethers'; -import { loadFixture, impersonateAccount, stopImpersonatingAccount, setBalance } from '@nomicfoundation/hardhat-network-helpers'; -import { TestERC20, AlgebraEternalFarming, NftPosManagerMock, FarmingCenter } from '../../typechain'; -import { algebraFixture, AlgebraFixtureType, mintPosition } from '../shared/fixtures'; -import { - expect, - getMaxTick, - getMinTick, - FeeAmount, - TICK_SPACINGS, - blockTimestamp, - BNe18, - ActorFixture, - makeTimestamps, - ZERO_ADDRESS, -} from '../shared'; -import { provider } from '../shared/provider'; -import { HelperCommands, ERC20Helper } from '../helpers'; -import { createTimeMachine } from '../shared/time'; -import { HelperTypes } from '../helpers/types'; -import { ContractParams } from '../../types/contractParams'; - -describe('unit/FarmingCenter', () => { - let actors: ActorFixture; - let lpUser0: Wallet; - let incentiveCreator: Wallet; - const amountDesired = BNe18(10); - const totalReward = 10000n; - const bonusReward = 200n; - const erc20Helper = new ERC20Helper(); - const Time = createTimeMachine(); - let helpers: HelperCommands; - let context: AlgebraFixtureType; - let timestamps: ContractParams.Timestamps; - let nonce = 0n; - - before(async () => { - const wallets = (await ethers.getSigners()) as any as Wallet[]; - actors = new ActorFixture(wallets, provider); - lpUser0 = actors.lpUser0(); - incentiveCreator = actors.incentiveCreator(); - }); - - beforeEach('create fixture loader', async () => { - context = await loadFixture(algebraFixture); - helpers = HelperCommands.fromTestContext(context, actors, provider); - }); - - describe('#connectVirtualPoolToPlugin', async () => { - it('cannot call connectVirtualPool directly', async () => { - await expect(context.farmingCenter.connectVirtualPoolToPlugin(context.pool01, context.pool01)).to.be.revertedWith('Only farming can call this'); - }); - - it('cannot connect zero address to pool', async () => { - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await expect( - context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(ZERO_ADDRESS, context.pluginObj, { from: eternalFarmingAddress }) - ).to.be.revertedWith('Zero address as virtual pool'); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - }); - - it('cannot connect virtual pool to invalid pool', async () => { - const newContext = await algebraFixture(); - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await expect( - context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, newContext.pluginObj, { from: eternalFarmingAddress }) - ).to.be.revertedWith('Invalid pool'); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - }); - - it('can connect virtual pool', async () => { - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - expect(await context.farmingCenter.virtualPoolAddresses(context.pool01)).to.not.be.eq(ZERO_ADDRESS); - }); - - it('cannot connect virtual pool if something already connected', async () => { - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await context.pluginFactory.setFarmingAddress(incentiveCreator); - await context.pluginObj.connect(incentiveCreator).setIncentive(eternalFarmingAddress); - await context.pluginFactory.setFarmingAddress(context.farmingCenter); - await expect( - context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }) - ).to.be.revertedWith('Another incentive is connected'); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - }); - }); - - describe('#disconnectVirtualPoolFromPlugin', async () => { - it('cannot call disconnectVirtualPoolFromPlugin directly', async () => { - await expect(context.farmingCenter.disconnectVirtualPoolFromPlugin(context.pool01, context.pool01)).to.be.revertedWith( - 'Only farming can call this' - ); - }); - - it('cannot disconnect zero address from pool', async () => { - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await expect( - context.farmingCenter.connect(fakeSigner).disconnectVirtualPoolFromPlugin(ZERO_ADDRESS, context.pluginObj, { from: eternalFarmingAddress }) - ).to.be.revertedWith('Zero address as virtual pool'); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - }); - - it('cannot disconnect virtual pool from invalid pool', async () => { - const newContext = await algebraFixture(); - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await expect( - context.farmingCenter - .connect(fakeSigner) - .disconnectVirtualPoolFromPlugin(context.pool01, newContext.pluginObj, { from: eternalFarmingAddress }) - ).to.be.revertedWith('Invalid pool'); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - }); - - it('can disconnect virtual pool', async () => { - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); - await context.farmingCenter - .connect(fakeSigner) - .disconnectVirtualPoolFromPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - expect(await context.farmingCenter.virtualPoolAddresses(context.pool01)).to.be.eq(ZERO_ADDRESS); - }); - - it('can disconnect virtual pool if something another connected', async () => { - const eternalFarmingAddress = await context.eternalFarming.getAddress(); - await impersonateAccount(eternalFarmingAddress); - await setBalance(eternalFarmingAddress, 10 ** 18); - const fakeSigner = await ethers.getSigner(eternalFarmingAddress); - await context.pluginFactory.setFarmingAddress(incentiveCreator); - await context.pluginObj.connect(incentiveCreator).setIncentive(eternalFarmingAddress); - await context.pluginFactory.setFarmingAddress(context.farmingCenter); - await context.farmingCenter - .connect(fakeSigner) - .disconnectVirtualPoolFromPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); - await setBalance(eternalFarmingAddress, 0); - await stopImpersonatingAccount(eternalFarmingAddress); - expect(await context.farmingCenter.virtualPoolAddresses(context.pool01)).to.be.eq(ZERO_ADDRESS); - }); - }); - - describe('#applyLiquidityDelta', () => { - let createIncentiveResultEternal: HelperTypes.CreateIncentive.Result; - let tokenIdEternal: string; - - beforeEach('setup', async () => { - timestamps = makeTimestamps(await blockTimestamp()); - const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); - - createIncentiveResultEternal = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce, - rewardRate: 100n, - bonusRewardRate: 50n, - }); - - await Time.setAndMine(timestamps.startTime + 100); - - const mintResultEternal = await helpers.mintDepositFarmFlow({ - lp: lpUser0, - tokensToFarm, - ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], - amountsToFarm: [amountDesired, amountDesired], - createIncentiveResult: createIncentiveResultEternal, - }); - tokenIdEternal = mintResultEternal.tokenId; - }); - - it('cannot use if not nonfungiblePosManager', async () => { - await expect(context.farmingCenter.applyLiquidityDelta(tokenIdEternal, 100)).to.be.revertedWith('Only nonfungiblePosManager'); - }); - - it('works if liquidity decreased', async () => { - await expect( - context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenIdEternal, - liquidity: 100, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ).to.emit(context.eternalFarming, 'FarmEntered'); - }); - - it('works if liquidity decreased and incentive detached', async () => { - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce: 0, - }); - - await expect( - context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenIdEternal, - liquidity: 5, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ).to.emit(context.eternalFarming, 'FarmEnded'); - - expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); - }); - - it('works if liquidity decreased and emergency activated', async () => { - await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); - - await expect( - context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenIdEternal, - liquidity: 5, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ) - .to.emit(context.eternalFarming, 'FarmEnded') - .to.not.emit(context.nft, 'FarmingFailed'); - - expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); - }); - - it('works if liquidity decreased and incentive deactivated automatically', async () => { - await context.pluginFactory.setFarmingAddress(actors.algebraRootUser().address); - - const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - const _tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: -120, - tickUpper: 120, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: nonce, - }, - _tokenId - ); - - await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(ZERO_ADDRESS); - - await helpers.moveTickTo({ direction: 'down', desiredValue: -160, trader: actors.farmingDeployer() }); - - await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(incentiveAddress); - - await helpers.moveTickTo({ direction: 'up', desiredValue: -140, trader: actors.farmingDeployer() }); - - await expect( - context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenIdEternal, - liquidity: 5, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ).to.emit(context.eternalFarming, 'FarmEnded'); - - expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); - }); - - it('works if liquidity decreased and incentive detached indirectly', async () => { - await context.pluginFactory.setFarmingAddress(actors.algebraRootUser().address); - - const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); - - const _tokenId = await mintPosition(context.nft.connect(lpUser0), { - token0: await context.token0.getAddress(), - token1: await context.token1.getAddress(), - fee: FeeAmount.MEDIUM, - tickLower: -120, - tickUpper: 120, - recipient: lpUser0.address, - amount0Desired: amountDesired, - amount1Desired: amountDesired, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }); - - await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); - await context.farmingCenter.connect(lpUser0).enterFarming( - { - pool: context.pool01, - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - nonce: nonce, - }, - _tokenId - ); - - await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(ZERO_ADDRESS); - - await helpers.moveTickTo({ direction: 'down', desiredValue: -160, trader: actors.farmingDeployer() }); - - await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(incentiveAddress); - - await expect( - context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenIdEternal, - liquidity: 5, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ).to.emit(context.eternalFarming, 'FarmEnded'); - - expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); - }); - - it('works if liquidity increased', async () => { - const _erc20Helper = new ERC20Helper(); - await _erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.tokens[0], context.tokens[1]], 100n, await context.nft.getAddress()); - - await expect( - context.nft.connect(lpUser0).increaseLiquidity({ - tokenId: tokenIdEternal, - amount0Desired: 100, - amount1Desired: 100, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ).to.emit(context.eternalFarming, 'FarmEntered'); - }); - - it('works if liquidity removed completely', async () => { - const liquidity = (await context.nft.positions(tokenIdEternal)).liquidity; - await expect( - context.nft.connect(lpUser0).decreaseLiquidity({ - tokenId: tokenIdEternal, - liquidity: liquidity, - amount0Min: 0, - amount1Min: 0, - deadline: (await blockTimestamp()) + 1000, - }) - ).to.emit(context.eternalFarming, 'FarmEnded'); - expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); - }); - - it('do nothing if nft position manager calls for invalid token', async () => { - const nftPosMockFactory = await ethers.getContractFactory('NftPosManagerMock'); - const nftPosMock = (await nftPosMockFactory.deploy()) as any as NftPosManagerMock; - - const farmingCenterFactory = await ethers.getContractFactory('FarmingCenter'); - const farmingCenter = (await farmingCenterFactory.deploy(ZERO_ADDRESS, nftPosMock)) as any as FarmingCenter; - - await nftPosMock.setPosition(0, { - nonce: 0, - operator: ZERO_ADDRESS, - poolId: 0, - tickLower: -60, - tickUpper: 60, - liquidity: 100, - feeGrowthInside0LastX128: 1, - feeGrowthInside1LastX128: 1, - tokensOwed0: 0, - tokensOwed1: 0, - }); - - await nftPosMock.applyLiquidityDeltaInFC(farmingCenter, 0, 100); - expect(await farmingCenter.deposits(0)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); - }); - }); - - describe('#exitFarming', async () => { - let createIncentiveResultEternal: HelperTypes.CreateIncentive.Result; - - let tokenIdEternal: string; - - beforeEach('setup', async () => { - const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); - - createIncentiveResultEternal = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce, - rewardRate: 100n, - bonusRewardRate: 50n, - }); - - const mintResultEternal = await helpers.mintDepositFarmFlow({ - lp: lpUser0, - tokensToFarm, - ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], - amountsToFarm: [amountDesired, amountDesired], - createIncentiveResult: createIncentiveResultEternal, - }); - tokenIdEternal = mintResultEternal.tokenId; - }); - - it('can exit if farmedIn changed forcefully', async () => { - await context.nft.setFarmingCenter(lpUser0); - await context.nft.connect(lpUser0).approveForFarming(tokenIdEternal, true, lpUser0.address); - await context.nft.connect(lpUser0).switchFarmingStatus(tokenIdEternal, true); - expect(await context.nft.tokenFarmedIn(tokenIdEternal)).to.be.eq(lpUser0.address); - - await context.farmingCenter.connect(lpUser0).exitFarming( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ); - }); - }); - - describe('#collectRewards', () => { - let createIncentiveResultEternal: HelperTypes.CreateIncentive.Result; - - let tokenIdEternal: string; - - let claimAndCheck: (token: TestERC20, from: Wallet, amount: bigint, expectedAmountRewardBalance?: bigint) => Promise; - - beforeEach('setup', async () => { - timestamps = makeTimestamps(await blockTimestamp()); - const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; - - await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); - - createIncentiveResultEternal = await helpers.createIncentiveFlow({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - totalReward, - bonusReward, - poolAddress: await context.poolObj.getAddress(), - nonce, - rewardRate: 100n, - bonusRewardRate: 50n, - }); - - await Time.setAndMine(timestamps.startTime + 100); - - const mintResultEternal = await helpers.mintDepositFarmFlow({ - lp: lpUser0, - tokensToFarm, - ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], - amountsToFarm: [amountDesired, amountDesired], - createIncentiveResult: createIncentiveResultEternal, - }); - tokenIdEternal = mintResultEternal.tokenId; - - const trader = actors.traderUser0(); - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 10, - }); - - claimAndCheck = async (token: TestERC20, from: Wallet, amount: bigint, expectedAmountRewardBalance?: bigint) => { - const rewards = await context.eternalFarming.rewards(from.address, token); - - let balanceOfTokenBefore = await token.balanceOf(from.address); - - await context.farmingCenter.connect(from).claimReward(token, from.address, amount); - - let balanceOfTokenAfter = await token.balanceOf(from.address); - - if (amount != 0n) expect(balanceOfTokenAfter - balanceOfTokenBefore).to.equal(amount); - else { - expect(balanceOfTokenAfter - balanceOfTokenBefore).to.equal(rewards); - } - - if (expectedAmountRewardBalance === undefined) expectedAmountRewardBalance = 0n; - - expect(await context.eternalFarming.rewards(from.address, token)).to.be.eq(expectedAmountRewardBalance); - }; - }); - - it('works', async () => { - let balanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - await erc20Helper.ensureBalancesAndApprovals( - incentiveCreator, - [context.rewardToken, context.bonusRewardToken], - BNe18(1), - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(incentiveCreator).addRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - BNe18(1), - BNe18(1) - ); - - const trader = actors.traderUser0(); - - await Time.set(timestamps.endTime + 1000); - - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 10, - }); - - await context.farmingCenter.connect(lpUser0).collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ); - - let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - expect(balanceAfter - balanceBefore).to.equal(189799); - expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(94599); - - await claimAndCheck(context.rewardToken, lpUser0, 189799n); - await claimAndCheck(context.bonusRewardToken, lpUser0, 94599n); - }); - - it('collect rewards after eternalFarming deactivate', async () => { - let balanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - await erc20Helper.ensureBalancesAndApprovals( - incentiveCreator, - [context.rewardToken, context.bonusRewardToken], - BNe18(1), - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(incentiveCreator).addRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - BNe18(1), - BNe18(1) - ); - - const trader = actors.traderUser0(); - - await Time.set(timestamps.endTime + 1000); - - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 10, - }); - - await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }); - - await context.farmingCenter.connect(lpUser0).collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ); - - let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - expect(balanceAfter - balanceBefore).to.equal(189799); - expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(94599); - - await claimAndCheck(context.rewardToken, lpUser0, 189799n); - await claimAndCheck(context.bonusRewardToken, lpUser0, 94599n); - }); - - it('cannot collect if not owner', async () => { - let balanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - await erc20Helper.ensureBalancesAndApprovals( - incentiveCreator, - [context.rewardToken, context.bonusRewardToken], - BNe18(1), - await context.eternalFarming.getAddress() - ); - - await context.eternalFarming.connect(incentiveCreator).addRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - BNe18(1), - BNe18(1) - ); - - const trader = actors.traderUser0(); - - await Time.set(timestamps.endTime + 1000); - - await helpers.makeTickGoFlow({ - trader, - direction: 'up', - desiredValue: 10, - }); - - await expect( - context.farmingCenter.collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ) - ).to.be.revertedWith('Not approved for token'); - - let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - expect(balanceAfter - balanceBefore).to.equal(0); - expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(0); - }); - - it('when requesting zero amount', async () => { - await Time.set(timestamps.endTime + 10000); - let balanceBeforeFirstCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceBeforeFirstCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - await context.farmingCenter.connect(lpUser0).collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ); - - let balanceBeforeSecondCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceBeforeSecondCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - await context.farmingCenter.connect(lpUser0).collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ); - - let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); - - expect(balanceAfter - balanceBeforeSecondCollect).to.equal(0); - expect(bonusBalanceAfter - bonusBalanceBeforeSecondCollect).to.equal(0); - - expect(await context.eternalFarming.rewards(lpUser0.address, context.rewardToken)).to.be.eq( - balanceBeforeSecondCollect - balanceBeforeFirstCollect - ); - expect(await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken)).to.be.eq( - bonusBalanceBeforeSecondCollect - bonusBalanceBeforeFirstCollect - ); - - await claimAndCheck(context.rewardToken, lpUser0, 0n); - await claimAndCheck(context.bonusRewardToken, lpUser0, 0n); - }); - - it('collect with non-existent incentive', async () => { - await expect( - context.farmingCenter.connect(lpUser0).collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool12, - nonce, - }, - tokenIdEternal - ) - ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'incentiveNotExist'); - }); - - it('collect with non-existent nft', async () => { - await context.farmingCenter.connect(lpUser0).exitFarming( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ); - - await expect( - context.farmingCenter.connect(lpUser0).collectRewards( - { - rewardToken: context.rewardToken, - bonusRewardToken: context.bonusRewardToken, - pool: context.pool01, - nonce, - }, - tokenIdEternal - ) - ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'farmDoesNotExist'); - }); - }); -}); +import { ethers } from 'hardhat'; +import { Wallet } from 'ethers'; +import { loadFixture, impersonateAccount, stopImpersonatingAccount, setBalance } from '@nomicfoundation/hardhat-network-helpers'; +import { TestERC20, AlgebraEternalFarming, NftPosManagerMock, FarmingCenter } from '../../typechain'; +import { algebraFixture, AlgebraFixtureType, mintPosition } from '../shared/fixtures'; +import { + expect, + getMaxTick, + getMinTick, + FeeAmount, + TICK_SPACINGS, + blockTimestamp, + BNe18, + ActorFixture, + makeTimestamps, + ZERO_ADDRESS, +} from '../shared'; +import { provider } from '../shared/provider'; +import { HelperCommands, ERC20Helper } from '../helpers'; +import { createTimeMachine } from '../shared/time'; +import { HelperTypes } from '../helpers/types'; +import { ContractParams } from '../../types/contractParams'; + +describe('unit/FarmingCenter', () => { + let actors: ActorFixture; + let lpUser0: Wallet; + let incentiveCreator: Wallet; + const amountDesired = BNe18(10); + const totalReward = 10000n; + const bonusReward = 200n; + const erc20Helper = new ERC20Helper(); + const Time = createTimeMachine(); + let helpers: HelperCommands; + let context: AlgebraFixtureType; + let timestamps: ContractParams.Timestamps; + let nonce = 0n; + + before(async () => { + const wallets = (await ethers.getSigners()) as any as Wallet[]; + actors = new ActorFixture(wallets, provider); + lpUser0 = actors.lpUser0(); + incentiveCreator = actors.incentiveCreator(); + }); + + beforeEach('create fixture loader', async () => { + context = await loadFixture(algebraFixture); + helpers = HelperCommands.fromTestContext(context, actors, provider); + }); + + describe('#connectVirtualPoolToPlugin', async () => { + it('cannot call connectVirtualPool directly', async () => { + await expect(context.farmingCenter.connectVirtualPoolToPlugin(context.pool01, context.pool01)).to.be.revertedWith('Only farming can call this'); + }); + + it('cannot connect zero address to pool', async () => { + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await expect( + context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(ZERO_ADDRESS, context.pluginObj, { from: eternalFarmingAddress }) + ).to.be.revertedWith('Zero address as virtual pool'); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + }); + + it('cannot connect virtual pool to invalid pool', async () => { + const newContext = await algebraFixture(); + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await expect( + context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, newContext.pluginObj, { from: eternalFarmingAddress }) + ).to.be.revertedWith('Invalid pool'); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + }); + + it('can connect virtual pool', async () => { + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + expect(await context.farmingCenter.virtualPoolAddresses(context.pool01)).to.not.be.eq(ZERO_ADDRESS); + }); + + it('cannot connect virtual pool if something already connected', async () => { + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await context.pluginFactory.setFarmingAddress(incentiveCreator); + await context.pluginObj.connect(incentiveCreator).setIncentive(eternalFarmingAddress); + await context.pluginFactory.setFarmingAddress(context.farmingCenter); + await expect( + context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }) + ).to.be.revertedWith('Another incentive is connected'); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + }); + }); + + describe('#disconnectVirtualPoolFromPlugin', async () => { + it('cannot call disconnectVirtualPoolFromPlugin directly', async () => { + await expect(context.farmingCenter.disconnectVirtualPoolFromPlugin(context.pool01, context.pool01)).to.be.revertedWith( + 'Only farming can call this' + ); + }); + + it('cannot disconnect zero address from pool', async () => { + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await expect( + context.farmingCenter.connect(fakeSigner).disconnectVirtualPoolFromPlugin(ZERO_ADDRESS, context.pluginObj, { from: eternalFarmingAddress }) + ).to.be.revertedWith('Zero address as virtual pool'); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + }); + + it('cannot disconnect virtual pool from invalid pool', async () => { + const newContext = await algebraFixture(); + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await expect( + context.farmingCenter + .connect(fakeSigner) + .disconnectVirtualPoolFromPlugin(context.pool01, newContext.pluginObj, { from: eternalFarmingAddress }) + ).to.be.revertedWith('Invalid pool'); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + }); + + it('can disconnect virtual pool', async () => { + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await context.farmingCenter.connect(fakeSigner).connectVirtualPoolToPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); + await context.farmingCenter + .connect(fakeSigner) + .disconnectVirtualPoolFromPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + expect(await context.farmingCenter.virtualPoolAddresses(context.pool01)).to.be.eq(ZERO_ADDRESS); + }); + + it('can disconnect virtual pool if something another connected', async () => { + const eternalFarmingAddress = await context.eternalFarming.getAddress(); + await impersonateAccount(eternalFarmingAddress); + await setBalance(eternalFarmingAddress, 10 ** 18); + const fakeSigner = await ethers.getSigner(eternalFarmingAddress); + await context.pluginFactory.setFarmingAddress(incentiveCreator); + await context.pluginObj.connect(incentiveCreator).setIncentive(eternalFarmingAddress); + await context.pluginFactory.setFarmingAddress(context.farmingCenter); + await context.farmingCenter + .connect(fakeSigner) + .disconnectVirtualPoolFromPlugin(context.pool01, context.pluginObj, { from: eternalFarmingAddress }); + await setBalance(eternalFarmingAddress, 0); + await stopImpersonatingAccount(eternalFarmingAddress); + expect(await context.farmingCenter.virtualPoolAddresses(context.pool01)).to.be.eq(ZERO_ADDRESS); + }); + }); + + describe('#applyLiquidityDelta', () => { + let createIncentiveResultEternal: HelperTypes.CreateIncentive.Result; + let tokenIdEternal: string; + + beforeEach('setup', async () => { + timestamps = makeTimestamps(await blockTimestamp()); + const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); + + createIncentiveResultEternal = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce, + rewardRate: 100n, + bonusRewardRate: 50n, + }); + + await Time.setAndMine(timestamps.startTime + 100); + + const mintResultEternal = await helpers.mintDepositFarmFlow({ + lp: lpUser0, + tokensToFarm, + ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], + amountsToFarm: [amountDesired, amountDesired], + createIncentiveResult: createIncentiveResultEternal, + }); + tokenIdEternal = mintResultEternal.tokenId; + }); + + it('cannot use if not nonfungiblePosManager', async () => { + await expect(context.farmingCenter.applyLiquidityDelta(tokenIdEternal, 100)).to.be.revertedWith('Only nonfungiblePosManager'); + }); + + it('works if liquidity decreased', async () => { + await expect( + context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenIdEternal, + liquidity: 100, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ).to.emit(context.eternalFarming, 'FarmEntered'); + }); + + it('works if liquidity decreased and incentive detached', async () => { + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce: 0, + }); + + await expect( + context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenIdEternal, + liquidity: 5, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ).to.emit(context.eternalFarming, 'FarmEnded'); + + expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); + }); + + it('works if liquidity decreased and emergency activated', async () => { + await context.eternalFarming.connect(actors.wallets[0]).setEmergencyWithdrawStatus(true); + + await expect( + context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenIdEternal, + liquidity: 5, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ) + .to.emit(context.eternalFarming, 'FarmEnded') + .to.not.emit(context.nft, 'FarmingFailed'); + + expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); + }); + + it('works if liquidity decreased and incentive deactivated automatically', async () => { + await context.pluginFactory.setFarmingAddress(actors.algebraRootUser().address); + + const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + const _tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: -120, + tickUpper: 120, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: nonce, + }, + _tokenId + ); + + await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(ZERO_ADDRESS); + + await helpers.moveTickTo({ direction: 'down', desiredValue: -160, trader: actors.farmingDeployer() }); + + await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(incentiveAddress); + + await helpers.moveTickTo({ direction: 'up', desiredValue: -140, trader: actors.farmingDeployer() }); + + await expect( + context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenIdEternal, + liquidity: 5, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ).to.emit(context.eternalFarming, 'FarmEnded'); + + expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); + }); + + it('works if liquidity decreased and incentive detached indirectly', async () => { + await context.pluginFactory.setFarmingAddress(actors.algebraRootUser().address); + + const incentiveAddress = await context.pluginObj.connect(actors.algebraRootUser()).incentive(); + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); + + const _tokenId = await mintPosition(context.nft.connect(lpUser0), { + token0: await context.token0.getAddress(), + token1: await context.token1.getAddress(), + fee: FeeAmount.MEDIUM, + tickLower: -120, + tickUpper: 120, + recipient: lpUser0.address, + amount0Desired: amountDesired, + amount1Desired: amountDesired, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }); + + await context.nft.connect(lpUser0).approveForFarming(_tokenId, true, context.farmingCenter); + await context.farmingCenter.connect(lpUser0).enterFarming( + { + pool: context.pool01, + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + nonce: nonce, + }, + _tokenId + ); + + await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(ZERO_ADDRESS); + + await helpers.moveTickTo({ direction: 'down', desiredValue: -160, trader: actors.farmingDeployer() }); + + await context.pluginObj.connect(actors.algebraRootUser()).setIncentive(incentiveAddress); + + await expect( + context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenIdEternal, + liquidity: 5, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ).to.emit(context.eternalFarming, 'FarmEnded'); + + expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); + }); + + it('works if liquidity increased', async () => { + const _erc20Helper = new ERC20Helper(); + await _erc20Helper.ensureBalancesAndApprovals(lpUser0, [context.tokens[0], context.tokens[1]], 100n, await context.nft.getAddress()); + + await expect( + context.nft.connect(lpUser0).increaseLiquidity({ + tokenId: tokenIdEternal, + amount0Desired: 100, + amount1Desired: 100, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ).to.emit(context.eternalFarming, 'FarmEntered'); + }); + + it('works if liquidity removed completely', async () => { + const liquidity = (await context.nft.positions(tokenIdEternal)).liquidity; + await expect( + context.nft.connect(lpUser0).decreaseLiquidity({ + tokenId: tokenIdEternal, + liquidity: liquidity, + amount0Min: 0, + amount1Min: 0, + deadline: (await blockTimestamp()) + 1000, + }) + ).to.emit(context.eternalFarming, 'FarmEnded'); + expect(await context.farmingCenter.deposits(tokenIdEternal)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); + }); + + it('do nothing if nft position manager calls for invalid token', async () => { + const nftPosMockFactory = await ethers.getContractFactory('NftPosManagerMock'); + const nftPosMock = (await nftPosMockFactory.deploy()) as any as NftPosManagerMock; + + const farmingCenterFactory = await ethers.getContractFactory('FarmingCenter'); + const farmingCenter = (await farmingCenterFactory.deploy(ZERO_ADDRESS, nftPosMock)) as any as FarmingCenter; + + await nftPosMock.setPosition(0, { + nonce: 0, + operator: ZERO_ADDRESS, + poolId: 0, + tickLower: -60, + tickUpper: 60, + liquidity: 100, + feeGrowthInside0LastX128: 1, + feeGrowthInside1LastX128: 1, + tokensOwed0: 0, + tokensOwed1: 0, + }); + + await nftPosMock.applyLiquidityDeltaInFC(farmingCenter, 0, 100); + expect(await farmingCenter.deposits(0)).to.be.eq('0x0000000000000000000000000000000000000000000000000000000000000000'); + }); + }); + + describe('#exitFarming', async () => { + let createIncentiveResultEternal: HelperTypes.CreateIncentive.Result; + + let tokenIdEternal: string; + + beforeEach('setup', async () => { + const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); + + createIncentiveResultEternal = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce, + rewardRate: 100n, + bonusRewardRate: 50n, + }); + + const mintResultEternal = await helpers.mintDepositFarmFlow({ + lp: lpUser0, + tokensToFarm, + ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], + amountsToFarm: [amountDesired, amountDesired], + createIncentiveResult: createIncentiveResultEternal, + }); + tokenIdEternal = mintResultEternal.tokenId; + }); + + it('can exit if farmedIn changed forcefully', async () => { + await context.nft.setFarmingCenter(lpUser0); + await context.nft.connect(lpUser0).approveForFarming(tokenIdEternal, true, lpUser0.address); + await context.nft.connect(lpUser0).switchFarmingStatus(tokenIdEternal, true); + expect(await context.nft.tokenFarmedIn(tokenIdEternal)).to.be.eq(lpUser0.address); + + await context.farmingCenter.connect(lpUser0).exitFarming( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ); + }); + }); + + describe('#collectRewards', () => { + let createIncentiveResultEternal: HelperTypes.CreateIncentive.Result; + + let tokenIdEternal: string; + + let claimAndCheck: (token: TestERC20, from: Wallet, amount: bigint, expectedAmountRewardBalance?: bigint) => Promise; + + beforeEach('setup', async () => { + timestamps = makeTimestamps(await blockTimestamp()); + const tokensToFarm = [context.token0, context.token1] as [TestERC20, TestERC20]; + + await erc20Helper.ensureBalancesAndApprovals(lpUser0, tokensToFarm, amountDesired, await context.nft.getAddress()); + + createIncentiveResultEternal = await helpers.createIncentiveFlow({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + totalReward, + bonusReward, + poolAddress: await context.poolObj.getAddress(), + nonce, + rewardRate: 100n, + bonusRewardRate: 50n, + }); + + await Time.setAndMine(timestamps.startTime + 100); + + const mintResultEternal = await helpers.mintDepositFarmFlow({ + lp: lpUser0, + tokensToFarm, + ticks: [getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM])], + amountsToFarm: [amountDesired, amountDesired], + createIncentiveResult: createIncentiveResultEternal, + }); + tokenIdEternal = mintResultEternal.tokenId; + + const trader = actors.traderUser0(); + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 10, + }); + + claimAndCheck = async (token: TestERC20, from: Wallet, amount: bigint, expectedAmountRewardBalance?: bigint) => { + const rewards = await context.eternalFarming.rewards(from.address, token); + + let balanceOfTokenBefore = await token.balanceOf(from.address); + + await context.farmingCenter.connect(from).claimReward(token, from.address, amount); + + let balanceOfTokenAfter = await token.balanceOf(from.address); + + if (amount != 0n) expect(balanceOfTokenAfter - balanceOfTokenBefore).to.equal(amount); + else { + expect(balanceOfTokenAfter - balanceOfTokenBefore).to.equal(rewards); + } + + if (expectedAmountRewardBalance === undefined) expectedAmountRewardBalance = 0n; + + expect(await context.eternalFarming.rewards(from.address, token)).to.be.eq(expectedAmountRewardBalance); + }; + }); + + it('works', async () => { + let balanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + await erc20Helper.ensureBalancesAndApprovals( + incentiveCreator, + [context.rewardToken, context.bonusRewardToken], + BNe18(1), + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(incentiveCreator).addRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + BNe18(1), + BNe18(1) + ); + + const trader = actors.traderUser0(); + + await Time.set(timestamps.endTime + 1000); + + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 10, + }); + + await context.farmingCenter.connect(lpUser0).collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ); + + let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + expect(balanceAfter - balanceBefore).to.equal(189799); + expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(94599); + + await claimAndCheck(context.rewardToken, lpUser0, 189799n); + await claimAndCheck(context.bonusRewardToken, lpUser0, 94599n); + }); + + it('collect rewards after eternalFarming deactivate', async () => { + let balanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + await erc20Helper.ensureBalancesAndApprovals( + incentiveCreator, + [context.rewardToken, context.bonusRewardToken], + BNe18(1), + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(incentiveCreator).addRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + BNe18(1), + BNe18(1) + ); + + const trader = actors.traderUser0(); + + await Time.set(timestamps.endTime + 1000); + + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 10, + }); + + await context.eternalFarming.connect(incentiveCreator).deactivateIncentive({ + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }); + + await context.farmingCenter.connect(lpUser0).collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ); + + let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + expect(balanceAfter - balanceBefore).to.equal(189799); + expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(94599); + + await claimAndCheck(context.rewardToken, lpUser0, 189799n); + await claimAndCheck(context.bonusRewardToken, lpUser0, 94599n); + }); + + it('cannot collect if not owner', async () => { + let balanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + await erc20Helper.ensureBalancesAndApprovals( + incentiveCreator, + [context.rewardToken, context.bonusRewardToken], + BNe18(1), + await context.eternalFarming.getAddress() + ); + + await context.eternalFarming.connect(incentiveCreator).addRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + BNe18(1), + BNe18(1) + ); + + const trader = actors.traderUser0(); + + await Time.set(timestamps.endTime + 1000); + + await helpers.makeTickGoFlow({ + trader, + direction: 'up', + desiredValue: 10, + }); + + await expect( + context.farmingCenter.collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ) + ).to.be.revertedWith('Not approved for token'); + + let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + expect(balanceAfter - balanceBefore).to.equal(0); + expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(0); + }); + + it('when requesting zero amount', async () => { + await Time.set(timestamps.endTime + 10000); + let balanceBeforeFirstCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceBeforeFirstCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + await context.farmingCenter.connect(lpUser0).collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ); + + let balanceBeforeSecondCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceBeforeSecondCollect: bigint = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + await context.farmingCenter.connect(lpUser0).collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ); + + let balanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); + let bonusBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + + expect(balanceAfter - balanceBeforeSecondCollect).to.equal(0); + expect(bonusBalanceAfter - bonusBalanceBeforeSecondCollect).to.equal(0); + + expect(await context.eternalFarming.rewards(lpUser0.address, context.rewardToken)).to.be.eq( + balanceBeforeSecondCollect - balanceBeforeFirstCollect + ); + expect(await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken)).to.be.eq( + bonusBalanceBeforeSecondCollect - bonusBalanceBeforeFirstCollect + ); + + await claimAndCheck(context.rewardToken, lpUser0, 0n); + await claimAndCheck(context.bonusRewardToken, lpUser0, 0n); + }); + + it('collect with non-existent incentive', async () => { + await expect( + context.farmingCenter.connect(lpUser0).collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool12, + nonce, + }, + tokenIdEternal + ) + ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'incentiveNotExist'); + }); + + it('collect with non-existent nft', async () => { + await context.farmingCenter.connect(lpUser0).exitFarming( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ); + + await expect( + context.farmingCenter.connect(lpUser0).collectRewards( + { + rewardToken: context.rewardToken, + bonusRewardToken: context.bonusRewardToken, + pool: context.pool01, + nonce, + }, + tokenIdEternal + ) + ).to.be.revertedWithCustomError(context.eternalFarming as AlgebraEternalFarming, 'farmDoesNotExist'); + }); + }); +}); From 099cdd2091fdecab6f7b73d587e8baa3e50097b8 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 14 Jan 2026 17:56:56 +0300 Subject: [PATCH 3/4] some fixes --- .github/workflows/echidna_plugin.yml | 47 ------------ .github/workflows/tests_plugin.yml | 29 ------- .husky/pre-commit | 1 - package-lock.json | 110 ++++++++++++++++++++++++++- package.json | 2 +- scripts/test/deployLocal.js | 2 - 6 files changed, 107 insertions(+), 84 deletions(-) delete mode 100644 .github/workflows/echidna_plugin.yml delete mode 100644 .github/workflows/tests_plugin.yml diff --git a/.github/workflows/echidna_plugin.yml b/.github/workflows/echidna_plugin.yml deleted file mode 100644 index f82de3cc1..000000000 --- a/.github/workflows/echidna_plugin.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Echidna Plugin - -on: - push: - branches: - - integral-v1.2.2 - pull_request: - branches: - - integral-v1.2.2 - -jobs: - UnitAsserts: - strategy: - matrix: - testName: - - AdaptiveFeeEchidnaTest - - VolatilityOracleEchidnaTest - - VolatilityOracleMathEchidnaTest - - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: 'npm' - - - name: Install dependencies - run: npm run ci-install - - - name: Compile contracts - run: npm run compile - working-directory: ./src/plugin - - - name: Run ${{ matrix.testName }} - uses: crytic/echidna-action@v2 - with: - solc-version: 0.8.20 - files: ./src/plugin/ - contract: ${{ matrix.testName }} - crytic-args: --hardhat-ignore-compile - solc-args: --evm-version paris - config: ./src/plugin/contracts/test/echidna/echidna.config.yml diff --git a/.github/workflows/tests_plugin.yml b/.github/workflows/tests_plugin.yml deleted file mode 100644 index 87667ca1f..000000000 --- a/.github/workflows/tests_plugin.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Autotests Plugin - -on: - push: - branches: - - integral-v1.2.2 - pull_request: - branches: - - integral-v1.2.2 - -jobs: - Autotests: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: ./src/plugin - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: 'npm' - cache-dependency-path: package-lock.json - - run: npm run bootstrap - working-directory: ./ - - run: npm run compile - - run: npm run test diff --git a/.husky/pre-commit b/.husky/pre-commit index 5e697e42d..8981c304f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,5 @@ npm run precommit --workspace=src/core --if-present npm run precommit --workspace=src/periphery --if-present -npm run precommit --workspace=src/plugin --if-present npm run precommit --workspace=src/farming --if-present git add docs diff --git a/package-lock.json b/package-lock.json index 9cdacbebe..ac5e7f9ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -571,9 +571,79 @@ "node": ">=0.1.90" } }, - "node_modules/@cryptoalgebra/integral-base-plugin": { - "resolved": "src/plugin", - "link": true + "node_modules/@cryptoalgebra/abstract-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/abstract-plugin/-/abstract-plugin-2.2.0.tgz", + "integrity": "sha512-qf7OeyDFE8PMQ197Ik+nqDNn9zvbLb7IcxizyCgrSJEfAi4VmyICr0AlXpsNPkesn8VidGDFQ6QavnHuwbtP5A==", + "dependencies": { + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4", + "@openzeppelin/contracts": "^4.9.3", + "@openzeppelin/contracts-upgradeable": "^4.9.3" + } + }, + "node_modules/@cryptoalgebra/alm-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/alm-plugin/-/alm-plugin-2.2.0.tgz", + "integrity": "sha512-XO6v2NDgjz9v+nBN5BR/3FCaYRF4SztG7893pFcvSffKqgP+QdvND0YETDXjo8xIhCwh9HZ7amDJzOuCLNlANA==", + "dependencies": { + "@cryptoalgebra/abstract-plugin": "^2.2.0", + "@cryptoalgebra/alm-vault": "1.2.2", + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4", + "@openzeppelin/contracts": "^4.9.3", + "@openzeppelin/contracts-upgradeable": "^4.9.3" + } + }, + "node_modules/@cryptoalgebra/alm-vault": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/alm-vault/-/alm-vault-1.2.2.tgz", + "integrity": "sha512-niS8oWbeEz4iMFClYKNw82ukMk8B2fjS++3iOSYB4HH/SaZNp/cvMsJzHHdwvWHIbeypDu7EuTudmAaOr2E64w==", + "dependencies": { + "@cryptoalgebra/farming-proxy-plugin": "^2.0.0", + "@cryptoalgebra/integral-core": "^1.2.9", + "@cryptoalgebra/integral-farming": "^1.2.4", + "@cryptoalgebra/integral-periphery": "^1.2.4", + "@cryptoalgebra/volatility-oracle-plugin": "^2.0.0", + "@openzeppelin/contracts": "4.9.3" + } + }, + "node_modules/@cryptoalgebra/default-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/default-plugin/-/default-plugin-2.1.0.tgz", + "integrity": "sha512-09HDdow5IfY3v+jPyL17XLGH1AAVyozmuEiT9bLAnkhPQPF0aTQqUg7ZA7dXOyGfry/YkYZjiFrNyuNPyZMXGw==", + "dependencies": { + "@cryptoalgebra/abstract-plugin": "^2.1.0", + "@cryptoalgebra/alm-plugin": "^2.1.0", + "@cryptoalgebra/dynamic-fee-plugin": "^2.1.0", + "@cryptoalgebra/farming-proxy-plugin": "^2.1.0", + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4", + "@cryptoalgebra/safety-switch-plugin": "^2.1.0", + "@cryptoalgebra/volatility-oracle-plugin": "^2.1.0" + } + }, + "node_modules/@cryptoalgebra/dynamic-fee-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/dynamic-fee-plugin/-/dynamic-fee-plugin-2.2.0.tgz", + "integrity": "sha512-Emr3CgRMqZfQpSaBCwty8noP7mbEB9TJ5EVRCry/wGhbBhNbDRQGBWaj8kMbwUSNiEuySkvYoQlCH0MNe3z70A==", + "dependencies": { + "@cryptoalgebra/abstract-plugin": "^2.2.0", + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4", + "@openzeppelin/contracts": "^4.9.3", + "@openzeppelin/contracts-upgradeable": "^4.9.3" + } + }, + "node_modules/@cryptoalgebra/farming-proxy-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/farming-proxy-plugin/-/farming-proxy-plugin-2.1.0.tgz", + "integrity": "sha512-3+clCkak0pg2O2HLMu3YO4ako/2ZJW4+az/5XdAPlUMcemSMrZaVotC+nhpl7IN3NHUdYaPe5UuU/noluEQ+Zg==", + "dependencies": { + "@cryptoalgebra/abstract-plugin": "^2.1.0", + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4" + } }, "node_modules/@cryptoalgebra/integral-core": { "resolved": "src/core", @@ -587,6 +657,30 @@ "resolved": "src/periphery", "link": true }, + "node_modules/@cryptoalgebra/safety-switch-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/safety-switch-plugin/-/safety-switch-plugin-2.2.0.tgz", + "integrity": "sha512-aJIZE2gsy6Ubfy14BRGxja0y91BO/aAcJwXbk58X0aS7NL2T5VpdlLNkaaTsCl2GTR5jmUcc0AmH+EaugCvcDg==", + "dependencies": { + "@cryptoalgebra/abstract-plugin": "^2.2.0", + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4", + "@openzeppelin/contracts": "^4.9.3", + "@openzeppelin/contracts-upgradeable": "^4.9.3" + } + }, + "node_modules/@cryptoalgebra/volatility-oracle-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/volatility-oracle-plugin/-/volatility-oracle-plugin-2.2.0.tgz", + "integrity": "sha512-BLFit/U2jiHyurFJ4DmJhBNJK1AlWlrXyncKEnx0FgEWp0Ixonb7IVTD0WRQNZ1vV5gUcvdBMO57H0a+nw1hBQ==", + "dependencies": { + "@cryptoalgebra/abstract-plugin": "^2.2.0", + "@cryptoalgebra/integral-core": "1.2.9", + "@cryptoalgebra/integral-periphery": "1.2.4", + "@openzeppelin/contracts": "^4.9.3", + "@openzeppelin/contracts-upgradeable": "^4.9.3" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -4248,6 +4342,12 @@ "integrity": "sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==", "license": "MIT" }, + "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz", + "integrity": "sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -18984,7 +19084,8 @@ "version": "1.2.4", "license": "GPL-3.0-or-later", "dependencies": { - "@cryptoalgebra/integral-base-plugin": "~1.2.4", + "@cryptoalgebra/default-plugin": "2.1.0", + "@cryptoalgebra/farming-proxy-plugin": "2.1.0", "@cryptoalgebra/integral-core": "~1.2.7", "@cryptoalgebra/integral-periphery": "~1.2.4", "@openzeppelin/contracts": "4.9.3" @@ -19078,6 +19179,7 @@ "src/plugin": { "name": "@cryptoalgebra/integral-base-plugin", "version": "1.2.4", + "extraneous": true, "license": "GPL-2.0-or-later", "dependencies": { "@cryptoalgebra/integral-core": "~1.2.8", diff --git a/package.json b/package.json index 80848a4e9..654abe383 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "scripts": { "prepare": "husky", "lint": "prettier \"src/**/contracts/**/*.sol\"", - "solhint": "npm run solhint --workspace=src/core --if-present && npm run solhint --workspace=src/periphery --if-present && npm run solhint --workspace=src/plugin --if-present && npm run solhint --workspace=src/farming --if-present", + "solhint": "npm run solhint --workspace=src/core --if-present && npm run solhint --workspace=src/periphery --if-present && npm run solhint --workspace=src/farming --if-present", "bootstrap": "npm install && npm run compile", "compile": "cd src/core && hardhat compile && cd ../periphery && hardhat compile && cd ../farming && hardhat compile", "ci-install": "npm ci", diff --git a/scripts/test/deployLocal.js b/scripts/test/deployLocal.js index 1121d9e0d..3f606797a 100644 --- a/scripts/test/deployLocal.js +++ b/scripts/test/deployLocal.js @@ -19,8 +19,6 @@ function encodePriceSqrt(reserve1, reserve0) { function deployProtocol(network) { execSync(`cd src/core && npx hardhat run --network ${network} scripts/deploy.js`, { stdio: 'inherit' }); - //execSync(`cd src/plugins && npx hardhat run --network ${network} scripts/deploy.js`, { stdio: 'inherit' }); - execSync(`cd src/periphery && npx hardhat run --network ${network} scripts/deploy.js`, { stdio: 'inherit' }); //execSync(`cd src/farming && npx hardhat run --network ${network} scripts/deploy.js`, { stdio: 'inherit' }); From 7739a4aa1e04ea4b9ace32d6f37d87b1cb78fdad Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 14 Jan 2026 18:02:09 +0300 Subject: [PATCH 4/4] update readme --- README.md | 2 -- src/README.md | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a69aa034..002bb9e69 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,11 @@ Innovative DEX with concentrated liquidity and customizable plugins.

Tests status Echidna status -Tests status Tests status

Echidna status Echidna status -Echidna status Echidna status

diff --git a/src/README.md b/src/README.md index c26378a2c..4bc45003f 100644 --- a/src/README.md +++ b/src/README.md @@ -11,3 +11,7 @@ This module contains smart contracts that can be used by users to easily interac ## Farming A separate module with onchain farming for Algebra protocol concentrated liquidity positions. + +## Plugins + +Plugins are maintained in a separate repository: [https://github.com/cryptoalgebra/plugins-monorepo](https://github.com/cryptoalgebra/plugins-monorepo)