diff --git a/audits/Bailsec_Algebra_Integralv1.2_Core_Update_Audit.pdf b/audits/Bailsec_Algebra_Integralv1.2_Core_Update_Audit.pdf new file mode 100644 index 000000000..600d74454 Binary files /dev/null and b/audits/Bailsec_Algebra_Integralv1.2_Core_Update_Audit.pdf differ diff --git a/audits/README.md b/audits/README.md index c99bc4f61..7947c618b 100644 --- a/audits/README.md +++ b/audits/README.md @@ -3,6 +3,7 @@ | Module | Auditor | Report | Auditor publication | | :---------------- | :---------------- | :------: | ----: | | Core | [MixBytes](https://mixbytes.io/) | [report](Core_audit_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Core/Algebra%20Finance%20Core%20Security%20Audit%20Report.pdf) | +| Core Integral v1.2 | [Bailsec](https://bailsec.io/) | [report](Bailsec_Algebra_Integralv1.2_Core_Update_Audit.pdf) | | | Farming | [MixBytes](https://mixbytes.io/) | [report](Farming_Plugin_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Farmings/Algebra%20Farmings%20Security%20Audit%20Report.pdf) | | Base plugin | [MixBytes](https://mixbytes.io/) | [report](Base_Plugin_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Plugins/Algebra%20Plugins%20Security%20Audit%20Report.pdf) | | Entire protocol, bug hunting | [Riley Holterhus](https://www.rileyholterhus.com/) | [report](https://github.com/cryptoalgebra/Algebra/blob/dev/audits/Riley_Holterhus_Algebra_Integral.pdf) | | diff --git a/docs/Contracts/Core/base/AlgebraPoolBase.md b/docs/Contracts/Core/base/AlgebraPoolBase.md index 6ad7f8061..266ecb097 100644 --- a/docs/Contracts/Core/base/AlgebraPoolBase.md +++ b/docs/Contracts/Core/base/AlgebraPoolBase.md @@ -267,7 +267,7 @@ function getCommunityFeePending() external view returns (uint128, uint128) The amounts of token0 and token1 that will be sent to the vault -*Developer note: Will be sent COMMUNITY_FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp* +*Developer note: Will be sent FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp* **Returns:** diff --git a/docs/Contracts/Core/interfaces/pool/IAlgebraPoolState.md b/docs/Contracts/Core/interfaces/pool/IAlgebraPoolState.md index 21ee2d263..b27fea7b3 100644 --- a/docs/Contracts/Core/interfaces/pool/IAlgebraPoolState.md +++ b/docs/Contracts/Core/interfaces/pool/IAlgebraPoolState.md @@ -129,7 +129,7 @@ function getCommunityFeePending() external view returns (uint128 communityFeePen The amounts of token0 and token1 that will be sent to the vault -*Developer note: Will be sent COMMUNITY_FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp* +*Developer note: Will be sent FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp* **Returns:** diff --git a/hardhat.base.config.ts b/hardhat.base.config.ts index 6e0ec93fa..babb1010b 100644 --- a/hardhat.base.config.ts +++ b/hardhat.base.config.ts @@ -72,6 +72,11 @@ export default { chainId: 34443, accounts: [`0x${MNEMONIC || '1000000000000000000000000000000000000000000000000000000000000000'}`], }, + holesky: { + url: `https://ethereum-holesky-rpc.publicnode.com`, + chainId: 17000, + accounts: [`0x${MNEMONIC || '1000000000000000000000000000000000000000000000000000000000000000'}`], + }, blastTestnet: { url: `https://blast-sepolia.blockpi.network/v1/rpc/public`, chainId: 168587773, diff --git a/src/core/contracts/AlgebraCommunityVault.sol b/src/core/contracts/AlgebraCommunityVault.sol index a2914a682..1f1f0f8c5 100644 --- a/src/core/contracts/AlgebraCommunityVault.sol +++ b/src/core/contracts/AlgebraCommunityVault.sol @@ -10,7 +10,7 @@ import './interfaces/vault/IAlgebraCommunityVault.sol'; /// @title Algebra community fee vault /// @notice Community fee from pools is sent here, if it is enabled /// @dev Role system is used to withdraw tokens -/// @dev Version: Algebra Integral 1.1 +/// @dev Version: Algebra Integral 1.2 contract AlgebraCommunityVault is IAlgebraCommunityVault { /// @dev The role can be granted in AlgebraFactory bytes32 public constant COMMUNITY_FEE_WITHDRAWER_ROLE = keccak256('COMMUNITY_FEE_WITHDRAWER'); diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index b0b6edb4e..f01b0e605 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -17,7 +17,7 @@ import '@openzeppelin/contracts/security/ReentrancyGuard.sol'; /// @title Algebra factory /// @notice Is used to deploy pools and its plugins -/// @dev Version: Algebra Integral 1.1 +/// @dev Version: Algebra Integral 1.2 contract AlgebraFactory is IAlgebraFactory, Ownable2Step, AccessControlEnumerable, ReentrancyGuard { /// @inheritdoc IAlgebraFactory bytes32 public constant override POOLS_ADMINISTRATOR_ROLE = keccak256('POOLS_ADMINISTRATOR'); // it`s here for the public visibility of the value @@ -57,7 +57,7 @@ contract AlgebraFactory is IAlgebraFactory, Ownable2Step, AccessControlEnumerabl /// @inheritdoc IAlgebraFactory /// @dev keccak256 of AlgebraPool init bytecode. Used to compute pool address deterministically - bytes32 public constant POOL_INIT_CODE_HASH = 0x4b9e4a8044ce5695e06fce9421a63b6f5c3db8a561eebb30ea4c775469e36eaf; + bytes32 public constant POOL_INIT_CODE_HASH = 0xb3fc09be5eb433d99b1ec89fd8435aaf5ffea75c1879e19028aa2414a14b3c85; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); @@ -97,8 +97,8 @@ contract AlgebraFactory is IAlgebraFactory, Ownable2Step, AccessControlEnumerabl } /// @inheritdoc IAlgebraFactory - function createPool(address tokenA, address tokenB) external override nonReentrant returns (address pool) { - return _createPool(address(0), msg.sender, tokenA, tokenB, ''); + function createPool(address tokenA, address tokenB, bytes calldata data) external override nonReentrant returns (address pool) { + return _createPool(address(0), msg.sender, tokenA, tokenB, data); } /// @inheritdoc IAlgebraFactory @@ -124,7 +124,7 @@ contract AlgebraFactory is IAlgebraFactory, Ownable2Step, AccessControlEnumerabl address plugin; if (deployer == address(0)) { if (address(defaultPluginFactory) != address(0)) { - plugin = defaultPluginFactory.beforeCreatePoolHook(computePoolAddress(token0, token1), creator, address(0), token0, token1, ''); + plugin = defaultPluginFactory.beforeCreatePoolHook(computePoolAddress(token0, token1), creator, address(0), token0, token1, data); } } else { plugin = IAlgebraPluginFactory(msg.sender).beforeCreatePoolHook( diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 6d25d03ea..734ae51ae 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -21,7 +21,7 @@ import './interfaces/IAlgebraFactory.sol'; /// @title Algebra concentrated liquidity pool /// @notice This contract is responsible for liquidity positions, swaps and flashloans -/// @dev Version: Algebra Integral 1.1 +/// @dev Version: Algebra Integral 1.2 contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positions, SwapCalculation, ReservesManager { using SafeCast for uint256; using SafeCast for uint128; @@ -115,7 +115,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } } - _changeReserves(int256(amount0), int256(amount1), 0, 0); + _changeReserves(int256(amount0), int256(amount1), 0, 0, 0, 0); emit Mint(msg.sender, recipient, bottomTick, topTick, liquidityActual, amount0, amount1); _unlock(); @@ -133,35 +133,66 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio int128 liquidityDelta = -int128(amount); - _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); + uint24 pluginFee = _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); _lock(); _updateReserves(); - Position storage position = getOrCreatePosition(msg.sender, bottomTick, topTick); + { + Position storage position = getOrCreatePosition(msg.sender, bottomTick, topTick); + + (amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta); + + if (pluginFee > 0) { + uint256 deltaPluginFeePending0; + uint256 deltaPluginFeePending1; - (amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta); + if (amount0 > 0) { + deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR); + amount0 -= deltaPluginFeePending0; + } + if (amount1 > 0) { + deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR); + amount1 -= deltaPluginFeePending1; + } + + _changeReserves(0, 0, 0, 0, deltaPluginFeePending0, deltaPluginFeePending1); + } - if (amount0 | amount1 != 0) { - // since we do not support tokens whose total supply can exceed uint128, these casts are safe - // and, theoretically, unchecked cast prevents a complete blocking of burn - (position.fees0, position.fees1) = (position.fees0 + uint128(amount0), position.fees1 + uint128(amount1)); + if (amount0 | amount1 != 0) { + // since we do not support tokens whose total supply can exceed uint128, these casts are safe + // and, theoretically, unchecked cast prevents a complete blocking of burn + (position.fees0, position.fees1) = (position.fees0 + uint128(amount0), position.fees1 + uint128(amount1)); + } } - if (amount | amount0 | amount1 != 0) emit Burn(msg.sender, bottomTick, topTick, amount, amount0, amount1); + if (amount | amount0 | amount1 != 0) emit Burn(msg.sender, bottomTick, topTick, amount, amount0, amount1, pluginFee); _unlock(); _afterModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, amount0, amount1, data); } - function _beforeModifyPos(address owner, int24 bottomTick, int24 topTick, int128 liquidityDelta, bytes calldata data) internal { + function _isPlugin() internal view returns (bool) { + return msg.sender == plugin; + } + + function _beforeModifyPos( + address owner, + int24 bottomTick, + int24 topTick, + int128 liquidityDelta, + bytes calldata data + ) internal returns (uint24 pluginFee) { if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_POSITION_MODIFY_FLAG)) { - IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data).shouldReturn( - IAlgebraPlugin.beforeModifyPosition.selector - ); + if (_isPlugin()) return 0; + bytes4 selector; + (selector, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data); + if (pluginFee >= 1e6) revert incorrectPluginFee(); + selector.shouldReturn(IAlgebraPlugin.beforeModifyPosition.selector); } } function _afterModifyPos(address owner, int24 bTick, int24 tTick, int128 deltaL, uint256 amount0, uint256 amount1, bytes calldata data) internal { + if (_isPlugin()) return; if (globalState.pluginConfig.hasFlag(Plugins.AFTER_POSITION_MODIFY_FLAG)) { IAlgebraPlugin(plugin).afterModifyPosition(msg.sender, owner, bTick, tTick, deltaL, amount0, amount1, data).shouldReturn( IAlgebraPlugin.afterModifyPosition.selector @@ -195,13 +226,19 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio if (amount0 > 0) _transfer(token0, recipient, amount0); if (amount1 > 0) _transfer(token1, recipient, amount1); - _changeReserves(-int256(uint256(amount0)), -int256(uint256(amount1)), 0, 0); + _changeReserves(-int256(uint256(amount0)), -int256(uint256(amount1)), 0, 0, 0, 0); } emit Collect(msg.sender, recipient, bottomTick, topTick, amount0, amount1); } _unlock(); } + struct SwapEventParams { + uint160 currentPrice; + int24 currentTick; + uint128 currentLiquidity; + } + /// @inheritdoc IAlgebraPoolActions function swap( address recipient, @@ -210,34 +247,47 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio uint160 limitSqrtPrice, bytes calldata data ) external override returns (int256 amount0, int256 amount1) { - _beforeSwap(recipient, zeroToOne, amountRequired, limitSqrtPrice, false, data); + (uint24 overrideFee, uint24 pluginFee) = _beforeSwap(recipient, zeroToOne, amountRequired, limitSqrtPrice, false, data); _lock(); { // scope to prevent "stack too deep" + SwapEventParams memory eventParams; + FeesAmount memory fees; + (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, fees) = _calculateSwap( + overrideFee, + pluginFee, + zeroToOne, + amountRequired, + limitSqrtPrice + ); (uint256 balance0Before, uint256 balance1Before) = _updateReserves(); - uint160 currentPrice; - int24 currentTick; - uint128 currentLiquidity; - uint256 communityFee; - (amount0, amount1, currentPrice, currentTick, currentLiquidity, communityFee) = _calculateSwap(zeroToOne, amountRequired, limitSqrtPrice); if (zeroToOne) { unchecked { if (amount1 < 0) _transfer(token1, recipient, uint256(-amount1)); // amount1 cannot be > 0 } _swapCallback(amount0, amount1, data); // callback to get tokens from the msg.sender if (balance0Before + uint256(amount0) > _balanceToken0()) revert insufficientInputAmount(); - _changeReserves(amount0, amount1, communityFee, 0); // reflect reserve change and pay communityFee + _changeReserves(amount0, amount1, fees.communityFeeAmount, 0, fees.pluginFeeAmount, 0); // reflect reserve change and pay communityFee } else { unchecked { if (amount0 < 0) _transfer(token0, recipient, uint256(-amount0)); // amount0 cannot be > 0 } _swapCallback(amount0, amount1, data); // callback to get tokens from the msg.sender if (balance1Before + uint256(amount1) > _balanceToken1()) revert insufficientInputAmount(); - _changeReserves(amount0, amount1, 0, communityFee); // reflect reserve change and pay communityFee + _changeReserves(amount0, amount1, 0, fees.communityFeeAmount, 0, fees.pluginFeeAmount); // reflect reserve change and pay communityFee } - _emitSwapEvent(recipient, amount0, amount1, currentPrice, currentLiquidity, currentTick); + _emitSwapEvent( + recipient, + amount0, + amount1, + eventParams.currentPrice, + eventParams.currentLiquidity, + eventParams.currentTick, + overrideFee, + pluginFee + ); } _unlock(); @@ -266,29 +316,33 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _swapCallback(amountToSell, 0, data); // callback to get tokens from the msg.sender uint256 balanceAfter = _balanceToken0(); amountReceived = (balanceAfter - balanceBefore).toInt256(); - _changeReserves(amountReceived, 0, 0, 0); + _changeReserves(amountReceived, 0, 0, 0, 0, 0); } else { uint256 balanceBefore = _balanceToken1(); _swapCallback(0, amountToSell, data); // callback to get tokens from the msg.sender uint256 balanceAfter = _balanceToken1(); amountReceived = (balanceAfter - balanceBefore).toInt256(); - _changeReserves(0, amountReceived, 0, 0); + _changeReserves(0, amountReceived, 0, 0, 0, 0); } if (amountReceived != amountToSell) amountToSell = amountReceived; } if (amountToSell == 0) revert insufficientInputAmount(); _unlock(); - _beforeSwap(recipient, zeroToOne, amountToSell, limitSqrtPrice, true, data); + (uint24 overrideFee, uint24 pluginFee) = _beforeSwap(recipient, zeroToOne, amountToSell, limitSqrtPrice, true, data); _lock(); _updateReserves(); - uint160 currentPrice; - int24 currentTick; - uint128 currentLiquidity; - uint256 communityFee; - (amount0, amount1, currentPrice, currentTick, currentLiquidity, communityFee) = _calculateSwap(zeroToOne, amountToSell, limitSqrtPrice); + SwapEventParams memory eventParams; + FeesAmount memory fees; + (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, fees) = _calculateSwap( + overrideFee, + pluginFee, + zeroToOne, + amountToSell, + limitSqrtPrice + ); unchecked { // transfer to the recipient @@ -296,36 +350,64 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio if (amount1 < 0) _transfer(token1, recipient, uint256(-amount1)); // amount1 cannot be > 0 uint256 leftover = uint256(amountToSell - amount0); // return the leftovers if (leftover != 0) _transfer(token0, leftoversRecipient, leftover); - _changeReserves(-leftover.toInt256(), amount1, communityFee, 0); // reflect reserve change and pay communityFee + _changeReserves(-leftover.toInt256(), amount1, fees.communityFeeAmount, 0, fees.pluginFeeAmount, 0); // reflect reserve change and pay communityFee } else { if (amount0 < 0) _transfer(token0, recipient, uint256(-amount0)); // amount0 cannot be > 0 uint256 leftover = uint256(amountToSell - amount1); // return the leftovers if (leftover != 0) _transfer(token1, leftoversRecipient, leftover); - _changeReserves(amount0, -leftover.toInt256(), 0, communityFee); // reflect reserve change and pay communityFee + _changeReserves(amount0, -leftover.toInt256(), 0, fees.communityFeeAmount, 0, fees.pluginFeeAmount); // reflect reserve change and pay communityFee } } - _emitSwapEvent(recipient, amount0, amount1, currentPrice, currentLiquidity, currentTick); + _emitSwapEvent( + recipient, + amount0, + amount1, + eventParams.currentPrice, + eventParams.currentLiquidity, + eventParams.currentTick, + overrideFee, + pluginFee + ); _unlock(); _afterSwap(recipient, zeroToOne, amountToSell, limitSqrtPrice, amount0, amount1, data); } /// @dev internal function to reduce bytecode size - function _emitSwapEvent(address recipient, int256 amount0, int256 amount1, uint160 newPrice, uint128 newLiquidity, int24 newTick) private { - emit Swap(msg.sender, recipient, amount0, amount1, newPrice, newLiquidity, newTick); + function _emitSwapEvent( + address recipient, + int256 amount0, + int256 amount1, + uint160 newPrice, + uint128 newLiquidity, + int24 newTick, + uint24 overrideFee, + uint24 pluginFee + ) private { + emit Swap(msg.sender, recipient, amount0, amount1, newPrice, newLiquidity, newTick, overrideFee, pluginFee); } - function _beforeSwap(address recipient, bool zto, int256 amount, uint160 limitPrice, bool payInAdvance, bytes calldata data) internal { + function _beforeSwap( + address recipient, + bool zto, + int256 amount, + uint160 limitPrice, + bool payInAdvance, + bytes calldata data + ) internal returns (uint24 overrideFee, uint24 pluginFee) { if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_SWAP_FLAG)) { - IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data).shouldReturn( - IAlgebraPlugin.beforeSwap.selector - ); + if (_isPlugin()) return (0, 0); + bytes4 selector; + (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data); + // we will check that fee is less than denominator inside the swap calculation + selector.shouldReturn(IAlgebraPlugin.beforeSwap.selector); } } function _afterSwap(address recipient, bool zto, int256 amount, uint160 limitPrice, int256 amount0, int256 amount1, bytes calldata data) internal { if (globalState.pluginConfig.hasFlag(Plugins.AFTER_SWAP_FLAG)) { + if (_isPlugin()) return; IAlgebraPlugin(plugin).afterSwap(msg.sender, recipient, zto, amount, limitPrice, amount0, amount1, data).shouldReturn( IAlgebraPlugin.afterSwap.selector ); @@ -373,7 +455,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio uint256 communityFee1; if (paid1 > 0) communityFee1 = FullMath.mulDiv(paid1, _communityFee, Constants.COMMUNITY_FEE_DENOMINATOR); - _changeReserves(int256(communityFee0), int256(communityFee1), communityFee0, communityFee1); + _changeReserves(int256(communityFee0), int256(communityFee1), communityFee0, communityFee1, 0, 0); } emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); } diff --git a/src/core/contracts/AlgebraPoolDeployer.sol b/src/core/contracts/AlgebraPoolDeployer.sol index 25a6ed710..449514f00 100644 --- a/src/core/contracts/AlgebraPoolDeployer.sol +++ b/src/core/contracts/AlgebraPoolDeployer.sol @@ -8,7 +8,7 @@ import './AlgebraPool.sol'; /// @title Algebra pool deployer /// @notice Is used by AlgebraFactory to deploy pools -/// @dev Version: Algebra Integral 1.1 +/// @dev Version: Algebra Integral 1.2 contract AlgebraPoolDeployer is IAlgebraPoolDeployer { /// @dev two storage slots for dense cache packing bytes32 private cache0; diff --git a/src/core/contracts/base/AlgebraPoolBase.sol b/src/core/contracts/base/AlgebraPoolBase.sol index 5184bdc9a..1fc657ca4 100644 --- a/src/core/contracts/base/AlgebraPoolBase.sol +++ b/src/core/contracts/base/AlgebraPoolBase.sol @@ -63,11 +63,14 @@ abstract contract AlgebraPoolBase is IAlgebraPool, Timestamp { /// @inheritdoc IAlgebraPoolState mapping(int24 => TickManagement.Tick) public override ticks; - /// @inheritdoc IAlgebraPoolState - uint32 public override communityFeeLastTimestamp; /// @dev The amounts of token0 and token1 that will be sent to the vault uint104 internal communityFeePending0; uint104 internal communityFeePending1; + /// @inheritdoc IAlgebraPoolState + uint32 public override lastFeeTransferTimestamp; + + uint104 internal pluginFeePending0; + uint104 internal pluginFeePending1; /// @inheritdoc IAlgebraPoolState address public override plugin; @@ -134,6 +137,10 @@ abstract contract AlgebraPoolBase is IAlgebraPool, Timestamp { return (communityFeePending0, communityFeePending1); } + function getPluginFeePending() external view override returns (uint128, uint128) { + return (pluginFeePending0, pluginFeePending1); + } + /// @inheritdoc IAlgebraPoolState function fee() external view override returns (uint16 currentFee) { currentFee = globalState.lastFee; diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index 4daf248f6..7a65c496a 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -2,12 +2,15 @@ pragma solidity =0.8.20; import '../libraries/SafeCast.sol'; +import '../libraries/Plugins.sol'; import './AlgebraPoolBase.sol'; - +import '../interfaces/plugin/IAlgebraPlugin.sol'; +import '../interfaces/pool/IAlgebraPoolErrors.sol'; /// @title Algebra reserves management abstract contract /// @notice Encapsulates logic for tracking and changing pool reserves /// @dev The reserve mechanism allows the pool to keep track of unexpected increases in balances abstract contract ReservesManager is AlgebraPoolBase { + using Plugins for bytes4; using SafeCast for uint256; /// @dev The tracked token0 and token1 reserves of pool @@ -66,35 +69,166 @@ abstract contract ReservesManager is AlgebraPoolBase { } } + /// @notice Accrues fees and transfers them to `recipient` + /// @dev If we transfer fees, writes zeros to the storage slot specified by the slot argument + /// If we do not transfer fees, returns actual pendingFees + function _accrueAndTransferFees( + uint256 fee0, + uint256 fee1, + uint256 lastTimestamp, + bytes32 receiverSlot, + bytes32 feePendingSlot + ) internal returns (uint104, uint104, uint256, uint256) { + if (fee0 | fee1 != 0) { + uint256 feePending0; + uint256 feePending1; + assembly { + // Load the storage slot specified by the slot argument + let sl := sload(feePendingSlot) + // Extract the uint104 value + feePending0 := and(sl, 0xFFFFFFFFFFFFFFFFFFFFFFFFFF) + // Shift right by 104 bits and extract the uint104 value + feePending1 := and(shr(104, sl), 0xFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + feePending0 += fee0; + feePending1 += fee1; + + if ( + _blockTimestamp() - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY || feePending0 > type(uint104).max || feePending1 > type(uint104).max + ) { + // use sload from slot (like pointer dereference) to avoid gas + address recipient; + assembly { + recipient := sload(receiverSlot) + } + (uint256 feeSent0, uint256 feeSent1) = _transferFees(feePending0, feePending1, recipient); + // use sload from slot (like pointer dereference) to avoid gas + // override `lastFeeTransferTimestamp` with zeros is OK + // because we will update it later + assembly { + sstore(feePendingSlot, 0) + } + // sent fees return 0 pending and sent fees + return (0, 0, feeSent0, feeSent1); + } else { + // didn't send fees return pending fees and 0 sent + return (uint104(feePending0), uint104(feePending1), 0, 0); + } + } else { + if (_blockTimestamp() - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) { + uint256 feePending0; + uint256 feePending1; + assembly { + // Load the storage slot specified by the slot argument + let sl := sload(feePendingSlot) + // Extract the uint104 value + feePending0 := and(sl, 0xFFFFFFFFFFFFFFFFFFFFFFFFFF) + // Shift right by 104 bits and extract the uint104 value + feePending1 := and(shr(104, sl), 0xFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + + if (feePending0 | feePending1 != 0) { + address recipient; + // use sload from slot (like pointer dereference) to avoid gas + assembly { + recipient := sload(receiverSlot) + } + (uint256 feeSent0, uint256 feeSent1) = _transferFees(feePending0, feePending1, recipient); + // use sload from slot (like pointer dereference) to avoid gas + assembly { + sstore(feePendingSlot, 0) + } + // sent fees return 0 pending and sent fees + return (0, 0, feeSent0, feeSent1); + } + } + // didn't either sent fees or increased pending + return (0, 0, 0, 0); + } + } + + function _transferFees(uint256 feePending0, uint256 feePending1, address feesRecipient) private returns (uint256, uint256) { + uint256 feeSent0; + uint256 feeSent1; + + if (feePending0 > 0) { + _transfer(token0, feesRecipient, feePending0); + feeSent0 = feePending0; + } + if (feePending1 > 0) { + _transfer(token1, feesRecipient, feePending1); + feeSent1 = feePending1; + } + + return (feeSent0, feeSent1); + } + /// @notice Applies deltas to reserves and pays communityFees /// @dev Community fee is sent to the vault at a specified frequency or when variables communityFeePending{0,1} overflow /// @param deltaR0 Amount of token0 to add/subtract to/from reserve0, must not exceed uint128 /// @param deltaR1 Amount of token1 to add/subtract to/from reserve1, must not exceed uint128 /// @param communityFee0 Amount of token0 to pay as communityFee, must not exceed uint128 /// @param communityFee1 Amount of token1 to pay as communityFee, must not exceed uint128 - function _changeReserves(int256 deltaR0, int256 deltaR1, uint256 communityFee0, uint256 communityFee1) internal { - if (communityFee0 | communityFee1 != 0) { - unchecked { - // overflow is desired since we do not support tokens with totalSupply > type(uint128).max - uint256 _cfPending0 = uint256(communityFeePending0) + communityFee0; - uint256 _cfPending1 = uint256(communityFeePending1) + communityFee1; - uint32 currentTimestamp = _blockTimestamp(); - // underflow in timestamps is desired - if ( - currentTimestamp - communityFeeLastTimestamp >= Constants.COMMUNITY_FEE_TRANSFER_FREQUENCY || - _cfPending0 > type(uint104).max || - _cfPending1 > type(uint104).max - ) { - address _communityVault = communityVault; - if (_cfPending0 > 0) _transfer(token0, _communityVault, _cfPending0); - if (_cfPending1 > 0) _transfer(token1, _communityVault, _cfPending1); - communityFeeLastTimestamp = currentTimestamp; - (deltaR0, deltaR1) = (deltaR0 - _cfPending0.toInt256(), deltaR1 - _cfPending1.toInt256()); - (_cfPending0, _cfPending1) = (0, 0); - } - // the previous block guarantees that no overflow occurs - (communityFeePending0, communityFeePending1) = (uint104(_cfPending0), uint104(_cfPending1)); + function _changeReserves( + int256 deltaR0, + int256 deltaR1, + uint256 communityFee0, + uint256 communityFee1, + uint256 pluginFee0, + uint256 pluginFee1 + ) internal { + if (communityFee0 > 0 || communityFee1 > 0 || pluginFee0 > 0 || pluginFee1 > 0) { + bytes32 feePendingSlot; + bytes32 feeRecipientSlot; + uint32 lastTimestamp = lastFeeTransferTimestamp; + bool feeSent; + + assembly { + feePendingSlot := communityFeePending0.slot + feeRecipientSlot := communityVault.slot + } + // pass feeRecipientSlot to avoid redundant sload of an address + (uint104 feePending0, uint104 feePending1, uint256 feeSent0, uint256 feeSent1) = _accrueAndTransferFees( + communityFee0, + communityFee1, + lastTimestamp, + feeRecipientSlot, + feePendingSlot + ); + if (feeSent0 | feeSent1 != 0) { + // sent fees so decrease deltas + (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); + feeSent = true; + } else { + // update pending if we accrued fees + if (feePending0 | feePending1 != 0) (communityFeePending0, communityFeePending1) = (feePending0, feePending1); + } + + assembly { + feePendingSlot := pluginFeePending0.slot + feeRecipientSlot := plugin.slot } + // pass feeRecipientSlot to avoid redundant sload of an address + (feePending0, feePending1, feeSent0, feeSent1) = _accrueAndTransferFees( + pluginFee0, + pluginFee1, + lastTimestamp, + feeRecipientSlot, + feePendingSlot + ); + if (feeSent0 | feeSent1 != 0) { + // sent fees so decrease deltas + (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); + feeSent = true; + + // notify plugin about sent fees + IAlgebraPlugin(plugin).handlePluginFee(feeSent0, feeSent1).shouldReturn(IAlgebraPlugin.handlePluginFee.selector); + } else { + // update pending if we accrued fees + if (feePending0 | feePending1 != 0) (pluginFeePending0, pluginFeePending1) = (feePending0, feePending1); + } + + if (feeSent) lastFeeTransferTimestamp = _blockTimestamp(); } if (deltaR0 | deltaR1 == 0) return; diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index 7102ff916..91dab597c 100644 --- a/src/core/contracts/base/SwapCalculation.sol +++ b/src/core/contracts/base/SwapCalculation.sol @@ -22,9 +22,10 @@ abstract contract SwapCalculation is AlgebraPoolBase { uint256 totalFeeGrowthInput; // The initial totalFeeGrowth + the fee growth during a swap uint256 totalFeeGrowthOutput; // The initial totalFeeGrowth for output token, should not change during swap bool exactInput; // Whether the exact input or output is specified - uint16 fee; // The current fee value in hundredths of a bip, i.e. 1e-6 + uint24 fee; // The current fee value in hundredths of a bip, i.e. 1e-6 int24 prevInitializedTick; // The previous initialized tick in linked list int24 nextInitializedTick; // The next initialized tick in linked list + uint24 pluginFee; } struct PriceMovementCache { @@ -35,16 +36,23 @@ abstract contract SwapCalculation is AlgebraPoolBase { uint256 feeAmount; // The total amount of fee earned within a current step } + struct FeesAmount { + uint256 communityFeeAmount; + uint256 pluginFeeAmount; + } + function _calculateSwap( + uint24 overrideFee, + uint24 pluginFee, bool zeroToOne, int256 amountRequired, uint160 limitSqrtPrice - ) internal returns (int256 amount0, int256 amount1, uint160 currentPrice, int24 currentTick, uint128 currentLiquidity, uint256 communityFeeAmount) { + ) internal returns (int256 amount0, int256 amount1, uint160 currentPrice, int24 currentTick, uint128 currentLiquidity, FeesAmount memory fees) { if (amountRequired == 0) revert zeroAmountRequired(); if (amountRequired == type(int256).min) revert invalidAmountRequired(); // to avoid problems when changing sign SwapCalculationCache memory cache; - (cache.amountRequiredInitial, cache.exactInput) = (amountRequired, amountRequired > 0); + (cache.amountRequiredInitial, cache.exactInput, cache.pluginFee) = (amountRequired, amountRequired > 0, pluginFee); // load from one storage slot (currentLiquidity, cache.prevInitializedTick, cache.nextInitializedTick) = (liquidity, prevTickGlobal, nextTickGlobal); @@ -52,6 +60,15 @@ abstract contract SwapCalculation is AlgebraPoolBase { // load from one storage slot too (currentPrice, currentTick, cache.fee, cache.communityFee) = (globalState.price, globalState.tick, globalState.lastFee, globalState.communityFee); if (currentPrice == 0) revert notInitialized(); + if (overrideFee != 0) { + cache.fee = overrideFee + pluginFee; + if (cache.fee >= 1e6) revert incorrectPluginFee(); + } else { + if (pluginFee != 0) { + cache.fee += pluginFee; + if (cache.fee >= 1e6) revert incorrectPluginFee(); + } + } if (zeroToOne) { if (limitSqrtPrice >= currentPrice || limitSqrtPrice <= TickMath.MIN_SQRT_RATIO) revert invalidLimitSqrtPrice(); @@ -88,10 +105,16 @@ abstract contract SwapCalculation is AlgebraPoolBase { cache.amountCalculated = cache.amountCalculated.add((step.input + step.feeAmount).toInt256()); // increase calculated input amount } + if (cache.pluginFee > 0 && cache.fee > 0) { + uint256 delta = FullMath.mulDiv(step.feeAmount, cache.pluginFee, cache.fee); + step.feeAmount -= delta; + fees.pluginFeeAmount += delta; + } + if (cache.communityFee > 0) { uint256 delta = (step.feeAmount.mul(cache.communityFee)) / Constants.COMMUNITY_FEE_DENOMINATOR; step.feeAmount -= delta; - communityFeeAmount += delta; + fees.communityFeeAmount += delta; } if (currentLiquidity > 0) cache.totalFeeGrowthInput += FullMath.mulDiv(step.feeAmount, Constants.Q128, currentLiquidity); diff --git a/src/core/contracts/interfaces/IAlgebraFactory.sol b/src/core/contracts/interfaces/IAlgebraFactory.sol index d8ad28dd8..44d2f5064 100644 --- a/src/core/contracts/interfaces/IAlgebraFactory.sol +++ b/src/core/contracts/interfaces/IAlgebraFactory.sol @@ -147,10 +147,11 @@ interface IAlgebraFactory { /// @notice Creates a pool for the given two tokens /// @param tokenA One of the two tokens in the desired pool /// @param tokenB The other of the two tokens in the desired pool + /// @param data Data for plugin creation /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. /// The call will revert if the pool already exists or the token arguments are invalid. /// @return pool The address of the newly created pool - function createPool(address tokenA, address tokenB) external returns (address pool); + function createPool(address tokenA, address tokenB, bytes calldata data) external returns (address pool); /// @notice Creates a custom pool for the given two tokens using `deployer` contract /// @param deployer The address of plugin deployer, also used for custom pool address calculation diff --git a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol index 7620d6859..3ddff3f87 100644 --- a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol +++ b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol @@ -9,6 +9,12 @@ interface IAlgebraPlugin { /// The last bit indicates whether the plugin contains dynamic fees logic function defaultPluginConfig() external view returns (uint8); + /// @notice Handle plugin fee transfer on plugin contract + /// @param pluginFee0 Fee0 amount transferred to plugin + /// @param pluginFee1 Fee1 amount transferred to plugin + /// @return bytes4 The function selector + function handlePluginFee(uint256 pluginFee0, uint256 pluginFee1) external returns (bytes4); + /// @notice The hook called before the state of a pool is initialized /// @param sender The initial msg.sender for the initialize call /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96 @@ -30,7 +36,7 @@ interface IAlgebraPlugin { /// @param topTick The upper tick of the position /// @param desiredLiquidityDelta The desired amount of liquidity to mint/burn /// @param data Data that passed through the callback - /// @return bytes4 The function selector for the hook + /// @return selector The function selector for the hook function beforeModifyPosition( address sender, address recipient, @@ -38,7 +44,7 @@ interface IAlgebraPlugin { int24 topTick, int128 desiredLiquidityDelta, bytes calldata data - ) external returns (bytes4); + ) external returns (bytes4 selector, uint24 pluginFee); /// @notice The hook called after a position is modified /// @param sender The initial msg.sender for the modify position call @@ -71,7 +77,7 @@ interface IAlgebraPlugin { /// value after the swap. If one for zero, the price cannot be greater than this value after the swap /// @param withPaymentInAdvance The flag indicating whether the `swapWithPaymentInAdvance` method was called /// @param data Data that passed through the callback - /// @return bytes4 The function selector for the hook + /// @return selector The function selector for the hook function beforeSwap( address sender, address recipient, @@ -80,7 +86,7 @@ interface IAlgebraPlugin { uint160 limitSqrtPrice, bool withPaymentInAdvance, bytes calldata data - ) external returns (bytes4); + ) external returns (bytes4 selector, uint24 feeOverride, uint24 pluginFee); /// @notice The hook called after a swap /// @param sender The initial msg.sender for the swap call diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol index 0f53938d6..f3a885824 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol @@ -25,6 +25,9 @@ interface IAlgebraPoolErrors { /// @notice Emitted if invalid amount is passed as amountRequired to swap function error invalidAmountRequired(); + /// @notice Emitted if plugin fee param greater than fee/override fee + error incorrectPluginFee(); + /// @notice Emitted if the pool received fewer tokens than it should have error insufficientInputAmount(); diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolEvents.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolEvents.sol index 4e6847c91..2385a2336 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolEvents.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolEvents.sol @@ -46,7 +46,16 @@ interface IAlgebraPoolEvents { /// @param liquidityAmount The amount of liquidity to remove /// @param amount0 The amount of token0 withdrawn /// @param amount1 The amount of token1 withdrawn - event Burn(address indexed owner, int24 indexed bottomTick, int24 indexed topTick, uint128 liquidityAmount, uint256 amount0, uint256 amount1); + /// @param pluginFee The fee to be sent to the plugin + event Burn( + address indexed owner, + int24 indexed bottomTick, + int24 indexed topTick, + uint128 liquidityAmount, + uint256 amount0, + uint256 amount1, + uint24 pluginFee + ); /// @notice Emitted by the pool for any swaps between token0 and token1 /// @param sender The address that initiated the swap call, and that received the callback @@ -56,7 +65,19 @@ interface IAlgebraPoolEvents { /// @param price The sqrt(price) of the pool after the swap, as a Q64.96 /// @param liquidity The liquidity of the pool after the swap /// @param tick The log base 1.0001 of price of the pool after the swap - event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick); + /// @param overrideFee The fee to be applied to the trade + /// @param pluginFee The fee to be sent to the plugin + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 price, + uint128 liquidity, + int24 tick, + uint24 overrideFee, + uint24 pluginFee + ); /// @notice Emitted by the pool for any flashes of token0/token1 /// @param sender The address that initiated the swap call, and that received the callback diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol index 11130fc75..8d6803479 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol @@ -68,16 +68,22 @@ interface IAlgebraPoolState { uint256 outerFeeGrowth1Token ); - /// @notice The timestamp of the last sending of tokens to community vault + /// @notice The timestamp of the last sending of tokens to vault/plugin /// @return The timestamp truncated to 32 bits - function communityFeeLastTimestamp() external view returns (uint32); + function lastFeeTransferTimestamp() external view returns (uint32); /// @notice The amounts of token0 and token1 that will be sent to the vault - /// @dev Will be sent COMMUNITY_FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp + /// @dev Will be sent FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp /// @return communityFeePending0 The amount of token0 that will be sent to the vault /// @return communityFeePending1 The amount of token1 that will be sent to the vault function getCommunityFeePending() external view returns (uint128 communityFeePending0, uint128 communityFeePending1); + /// @notice The amounts of token0 and token1 that will be sent to the plugin + /// @dev Will be sent FEE_TRANSFER_FREQUENCY after feeLastTransferTimestamp + /// @return pluginFeePending0 The amount of token0 that will be sent to the plugin + /// @return pluginFeePending1 The amount of token1 that will be sent to the plugin + function getPluginFeePending() external view returns (uint128 pluginFeePending0, uint128 pluginFeePending1); + /// @notice Returns the address of currently used plugin /// @dev The plugin is subject to change /// @return pluginAddress The address of currently used plugin diff --git a/src/core/contracts/libraries/Constants.sol b/src/core/contracts/libraries/Constants.sol index d782401ac..253d14741 100644 --- a/src/core/contracts/libraries/Constants.sol +++ b/src/core/contracts/libraries/Constants.sol @@ -18,7 +18,7 @@ library Constants { int24 internal constant MIN_TICK_SPACING = 1; // the frequency with which the accumulated community fees are sent to the vault - uint32 internal constant COMMUNITY_FEE_TRANSFER_FREQUENCY = 8 hours; + uint32 internal constant FEE_TRANSFER_FREQUENCY = 8 hours; // max(uint128) / (MAX_TICK - MIN_TICK) uint128 internal constant MAX_LIQUIDITY_PER_TICK = 191757638537527648490752896198553; diff --git a/src/core/contracts/libraries/PriceMovementMath.sol b/src/core/contracts/libraries/PriceMovementMath.sol index e4f7860aa..34247c021 100644 --- a/src/core/contracts/libraries/PriceMovementMath.sol +++ b/src/core/contracts/libraries/PriceMovementMath.sol @@ -113,7 +113,7 @@ library PriceMovementMath { uint160 targetPrice, uint128 liquidity, int256 amountAvailable, - uint16 fee + uint24 fee ) internal pure returns (uint160 resultPrice, uint256 input, uint256 output, uint256 feeAmount) { unchecked { function(uint160, uint160, uint128) pure returns (uint256) getInputTokenAmount = zeroToOne ? getInputTokenDelta01 : getInputTokenDelta10; diff --git a/src/core/contracts/test/MockDefaultPluginFactory.sol b/src/core/contracts/test/MockDefaultPluginFactory.sol index 25dc1407a..7d5590103 100644 --- a/src/core/contracts/test/MockDefaultPluginFactory.sol +++ b/src/core/contracts/test/MockDefaultPluginFactory.sol @@ -9,10 +9,13 @@ import './MockPoolPlugin.sol'; contract MockDefaultPluginFactory is IAlgebraPluginFactory { mapping(address => address) public pluginsForPools; + event DataOnPoolCreation(bytes data); + function afterCreatePoolHook(address plugin, address pool, address deployer) external override {} - function beforeCreatePoolHook(address pool, address, address, address, address, bytes calldata) external override returns (address plugin) { + function beforeCreatePoolHook(address pool, address, address, address, address, bytes calldata data) external override returns (address plugin) { plugin = address(new MockPoolPlugin(pool)); pluginsForPools[pool] = plugin; + emit DataOnPoolCreation(data); } } diff --git a/src/core/contracts/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index 35a0dde34..cf3b1052d 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -6,10 +6,14 @@ import '../interfaces/plugin/IAlgebraPlugin.sol'; import '../interfaces/plugin/IAlgebraDynamicFeePlugin.sol'; import '../interfaces/IAlgebraPool.sol'; import '../libraries/Plugins.sol'; +import './TestERC20.sol'; contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { address public pool; uint8 public selectorsDisableConfig; + uint24 public overrideFee; + uint24 public pluginFee; + bool public isDisabled; constructor(address _pool) { pool = _pool; @@ -60,6 +64,19 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { selectorsDisableConfig = newSelectorsDisableConfig; } + function handlePluginFee(uint256, uint256) external view override returns (bytes4 selector) { + if (isDisabled) return selector; + return IAlgebraPlugin.handlePluginFee.selector; + } + + function setPluginFees(uint24 _overrideFee, uint24 _pluginFee) external { + (overrideFee, pluginFee) = (_overrideFee, _pluginFee); + } + + function disablePluginFeeHandle() external { + isDisabled = true; + } + /// @notice The hook called before the state of a pool is initialized /// @param sender The initial msg.sender for the initialize call /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96 @@ -91,10 +108,11 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { int24 topTick, int128 desiredLiquidityDelta, bytes calldata data - ) external override returns (bytes4) { + ) external override returns (bytes4, uint24) { emit BeforeModifyPosition(sender, recipient, bottomTick, topTick, desiredLiquidityDelta, data); - if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.BEFORE_POSITION_MODIFY_FLAG)) return IAlgebraPlugin.beforeModifyPosition.selector; - return IAlgebraPlugin.defaultPluginConfig.selector; + if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.BEFORE_POSITION_MODIFY_FLAG)) + return (IAlgebraPlugin.beforeModifyPosition.selector, pluginFee); + return (IAlgebraPlugin.defaultPluginConfig.selector, pluginFee); } /// @notice The hook called after a position is modified @@ -126,10 +144,10 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { uint160 limitSqrtPrice, bool withPaymentInAdvance, bytes calldata data - ) external override returns (bytes4) { + ) external override returns (bytes4, uint24, uint24) { emit BeforeSwap(sender, recipient, zeroToOne, amountRequired, limitSqrtPrice, withPaymentInAdvance, data); - if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.BEFORE_SWAP_FLAG)) return IAlgebraPlugin.beforeSwap.selector; - return IAlgebraPlugin.defaultPluginConfig.selector; + if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.BEFORE_SWAP_FLAG)) return (IAlgebraPlugin.beforeSwap.selector, overrideFee, pluginFee); + return (IAlgebraPlugin.defaultPluginConfig.selector, overrideFee, pluginFee); } /// @notice The hook called after a swap @@ -180,4 +198,29 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.AFTER_FLASH_FLAG)) return IAlgebraPlugin.afterFlash.selector; return IAlgebraPlugin.defaultPluginConfig.selector; } + + function swap() external { + IAlgebraPool(pool).swap(address(this), true, 10000, 4295128740, ''); + } + + function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata ) external { + require(amount0Delta > 0 || amount1Delta > 0, 'Zero liquidity swap'); // swaps entirely within 0-liquidity regions are not supported + + (address token, uint256 amountToPay) = amount0Delta > 0 + ? (IAlgebraPool(pool).token0(), uint256(amount0Delta)) + : (IAlgebraPool(pool).token1(), uint256(amount1Delta)); + + TestERC20(token).transfer(pool, amountToPay); + } + + + function mint() external { + IAlgebraPool(pool).mint(address(this), address(this), -60, 60, 1000, ''); + } + + function algebraMintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata ) external { + + if (amount0Owed > 0) TestERC20(IAlgebraPool(pool).token0()).transfer(pool, amount0Owed); + if (amount1Owed > 0) TestERC20(IAlgebraPool(pool).token1()).transfer(pool, amount1Owed); + } } diff --git a/src/core/contracts/test/PriceMovementMathTest.sol b/src/core/contracts/test/PriceMovementMathTest.sol index 504720f7c..3a6228087 100644 --- a/src/core/contracts/test/PriceMovementMathTest.sol +++ b/src/core/contracts/test/PriceMovementMathTest.sol @@ -11,7 +11,7 @@ contract PriceMovementMathTest { uint160 sqrtPTarget, uint128 liquidity, int256 amountRemaining, - uint16 feePips + uint24 feePips ) external pure returns (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) { return PriceMovementMath.movePriceTowardsTarget(sqrtPTarget < sqrtP, sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); } @@ -21,7 +21,7 @@ contract PriceMovementMathTest { uint160 sqrtPTarget, uint128 liquidity, int256 amountRemaining, - uint16 feePips + uint24 feePips ) external view returns (uint256) { unchecked { uint256 gasBefore = gasleft(); diff --git a/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol b/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol index 02149a99e..2d313c047 100644 --- a/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol +++ b/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol @@ -10,11 +10,11 @@ contract PriceMovementMathEchidnaTest { uint160 sqrtPriceTargetRaw, uint128 liquidity, int256 amountRemaining, - uint16 feePips + uint24 feePips ) external pure { require(sqrtPriceRaw > 0); require(sqrtPriceTargetRaw > 0); - require(feePips <= 1e6); + require(feePips < 1e6); (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = PriceMovementMath.movePriceTowardsTarget( sqrtPriceTargetRaw <= sqrtPriceRaw, @@ -30,7 +30,6 @@ contract PriceMovementMathEchidnaTest { if (amountRemaining < 0) { assert(amountOut <= uint256(-amountRemaining)); - assert(amountIn >= feeAmount); assert(feeAmount <= FullMath.mulDivRoundingUp(amountIn, feePips, 1000000 - feePips)); } else { assert(amountIn + feeAmount <= uint256(amountRemaining)); diff --git a/src/core/hardhat.config.ts b/src/core/hardhat.config.ts index 212fe5919..ea05d8923 100644 --- a/src/core/hardhat.config.ts +++ b/src/core/hardhat.config.ts @@ -39,7 +39,7 @@ const HIGH_COMPILER_SETTINGS: SolcUserConfig = { evmVersion: 'paris', optimizer: { enabled: true, - runs: 1600, + runs: 0, }, metadata: { bytecodeHash: 'none', diff --git a/src/core/package-lock.json b/src/core/package-lock.json index 54bd07b6d..52be15bb8 100644 --- a/src/core/package-lock.json +++ b/src/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cryptoalgebra/integral-core", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cryptoalgebra/integral-core", - "version": "1.1.0", + "version": "1.2.0", "license": "GPL-2.0-or-later", "dependencies": { "@openzeppelin/contracts": "4.9.3" diff --git a/src/core/package.json b/src/core/package.json index 2a4cead3a..dcddfd481 100644 --- a/src/core/package.json +++ b/src/core/package.json @@ -5,7 +5,7 @@ "publishConfig": { "access": "public" }, - "version": "1.1.0", + "version": "1.2.0", "keywords": [ "algebra" ], diff --git a/src/core/test/AlgebraFactory.spec.ts b/src/core/test/AlgebraFactory.spec.ts index dda721b3f..1d59fe0ac 100644 --- a/src/core/test/AlgebraFactory.spec.ts +++ b/src/core/test/AlgebraFactory.spec.ts @@ -97,7 +97,7 @@ describe('AlgebraFactory', () => { }); it('pool bytecode size [ @skip-on-coverage ]', async () => { - await factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1]); + await factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], '0x'); const poolAddress = getCreate2Address( await poolDeployer.getAddress(), [TEST_ADDRESSES[0], TEST_ADDRESSES[1]], @@ -108,12 +108,12 @@ describe('AlgebraFactory', () => { async function createAndCheckPool(tokens: [string, string]) { const create2Address = getCreate2Address(await poolDeployer.getAddress(), tokens, poolBytecode); - const create = factory.createPool(tokens[0], tokens[1]); + const create = factory.createPool(tokens[0], tokens[1], '0x'); await expect(create).to.emit(factory, 'Pool'); - await expect(factory.createPool(tokens[0], tokens[1])).to.be.reverted; - await expect(factory.createPool(tokens[1], tokens[0])).to.be.reverted; + await expect(factory.createPool(tokens[0], tokens[1], '0x')).to.be.reverted; + await expect(factory.createPool(tokens[1], tokens[0], '0x')).to.be.reverted; expect(await factory.poolByPair(tokens[0], tokens[1]), 'getPool in order').to.eq(create2Address); expect(await factory.poolByPair(tokens[1], tokens[0]), 'getPool in reverse').to.eq(create2Address); @@ -170,6 +170,13 @@ describe('AlgebraFactory', () => { expect(await pool.plugin()).to.be.eq(pluginAddress); }); + it('data passed to defaultPluginFactory', async () => { + await factory.setDefaultPluginFactory(defaultPluginFactory); + const create = factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], '0x0200'); + + await expect(create).to.emit(defaultPluginFactory, 'DataOnPoolCreation').withArgs('0x0200'); + }); + it('sets vault in pool', async () => { await createAndCheckPool([TEST_ADDRESSES[0], TEST_ADDRESSES[1]]); @@ -198,22 +205,22 @@ describe('AlgebraFactory', () => { }); it('fails if token a == token b', async () => { - await expect(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[0])).to.be.reverted; + await expect(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[0], '0x')).to.be.reverted; }); it('fails if token a is 0 or token b is 0', async () => { - await expect(factory.createPool(TEST_ADDRESSES[0], ZeroAddress)).to.be.reverted; - await expect(factory.createPool(ZeroAddress, TEST_ADDRESSES[0])).to.be.reverted; - expect(factory.createPool(ZeroAddress, ZeroAddress)).to.be.revertedWithoutReason; + await expect(factory.createPool(TEST_ADDRESSES[0], ZeroAddress, '0x')).to.be.reverted; + await expect(factory.createPool(ZeroAddress, TEST_ADDRESSES[0], '0x')).to.be.reverted; + expect(factory.createPool(ZeroAddress, ZeroAddress, '0x')).to.be.revertedWithoutReason; }); it('gas [ @skip-on-coverage ]', async () => { - await snapshotGasCost(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1])); + await snapshotGasCost(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], '0x')); }); it('gas for second pool [ @skip-on-coverage ]', async () => { - await factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1]); - await snapshotGasCost(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[2])); + await factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], '0x'); + await snapshotGasCost(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[2], '0x')); }); }); diff --git a/src/core/test/AlgebraPool.gas.spec.ts b/src/core/test/AlgebraPool.gas.spec.ts index 97dbba344..ce2e21668 100644 --- a/src/core/test/AlgebraPool.gas.spec.ts +++ b/src/core/test/AlgebraPool.gas.spec.ts @@ -1,7 +1,7 @@ import { ethers } from 'hardhat'; import { Wallet } from 'ethers'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { MockTimeAlgebraPool, AlgebraPool } from '../typechain'; +import { MockTimeAlgebraPool, AlgebraPool, MockPoolPlugin } from '../typechain'; import { expect } from './shared/expect'; import { poolFixture } from './shared/fixtures'; @@ -47,7 +47,7 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { }); }); - for (const communityFee of [0, 60]) { + for (const communityFee of [0, 500]) { describe(communityFee > 0 ? 'fee is on' : 'fee is off', () => { const startingPrice = encodePriceSqrt(100001, 100000); const startingTick = 0; @@ -93,6 +93,7 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { let swapExact1For0: SwapFunction; let swapToHigherPrice: SwapToPriceFunction; let swapToLowerPrice: SwapToPriceFunction; + let poolPlugin: MockPoolPlugin; let pool: MockTimeAlgebraPool; let mint: MintFunction; @@ -104,7 +105,7 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { describe('#swapExact1For0', () => { it('first swap in block with no tick movement', async () => { - await snapshotGasCost(swapExact1For0(2000, wallet.address)); + await snapshotGasCost(swapExact1For0(4000, wallet.address)); expect((await pool.globalState()).price).to.not.eq(startingPrice); expect((await pool.globalState()).tick).to.eq(startingTick); }); @@ -122,9 +123,49 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { }); }) + describe('#swapExact1For0 with plugin fee on', () => { + beforeEach('load the fixture', async () => { + const MockPoolPluginFactory = await ethers.getContractFactory('MockPoolPlugin'); + poolPlugin = (await MockPoolPluginFactory.deploy(await pool.getAddress())) as any as MockPoolPlugin; + await poolPlugin.setPluginFees(0, 1000); + await pool.setPlugin(poolPlugin); + await pool.setPluginConfig(255); + + await pool.advanceTime(86400); + await swapExact0For1(expandTo18Decimals(1), wallet.address); + await pool.advanceTime(1); + await swapToHigherPrice(startingPrice, wallet.address); + expect((await pool.globalState()).tick).to.eq(startingTick); + expect((await pool.globalState()).price).to.eq(startingPrice); + }); + + it('first swap in block with no tick movement, without transfer', async () => { + await swapExact1For0(10000, wallet.address) + await pool.advanceTime(1) + await snapshotGasCost(swapExact1For0(10000, wallet.address)); + expect((await pool.globalState()).price).to.not.eq(startingPrice); + expect((await pool.globalState()).tick).to.eq(startingTick); + expect((await pool.getPluginFeePending())[1]).to.be.gt(0) + }); + + it('first swap in block with no tick movement, with transfer', async () => { + await pool.advanceTime(86400) + await snapshotGasCost(swapExact1For0(10000, 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, with transfer', async () => { + await pool.advanceTime(86400) + await snapshotGasCost(swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address)); + expect((await pool.getPluginFeePending())[1]).to.be.eq(0) + 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)); + await snapshotGasCost(swapExact0For1(4000, wallet.address)); expect((await pool.globalState()).price).to.not.eq(startingPrice); expect((await pool.globalState()).tick).to.eq(startingTick); }); diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 59d9dff2c..0a7b4dc7b 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -37,6 +37,7 @@ import { PriceMovementMathTest, IERC20Minimal, } from '../typechain'; +import { plugin } from '../typechain/contracts/interfaces'; type ThenArg = T extends PromiseLike ? U : T; @@ -1130,7 +1131,7 @@ describe('AlgebraPool', () => { await swapExact1For0(expandTo18Decimals(2), other.address); await expect(pool.burn(0, 120, expandTo18Decimals(1), '0x')) .to.emit(pool, 'Burn') - .withArgs(wallet.address, 0, 120, expandTo18Decimals(1), 0, '6017734268818165') + .withArgs(wallet.address, 0, 120, expandTo18Decimals(1), 0, '6017734268818165', 0) .to.not.emit(token0, 'Transfer') .to.not.emit(token1, 'Transfer'); await expect(pool.collect(wallet.address, 0, 120, MaxUint128, MaxUint128)) @@ -1148,7 +1149,7 @@ describe('AlgebraPool', () => { await swapExact0For1(expandTo18Decimals(2), other.address); await expect(pool.burn(-120, 0, expandTo18Decimals(1), '0x')) .to.emit(pool, 'Burn') - .withArgs(wallet.address, -120, 0, expandTo18Decimals(1), '6017734268818165', 0) + .withArgs(wallet.address, -120, 0, expandTo18Decimals(1), '6017734268818165', 0, 0) .to.not.emit(token0, 'Transfer') .to.not.emit(token1, 'Transfer'); await expect(pool.collect(wallet.address, -120, 0, MaxUint128, MaxUint128)) @@ -1168,7 +1169,7 @@ describe('AlgebraPool', () => { await swapExact1For0(expandTo18Decimals(2), other.address); await expect(pool.burn(0, 120, expandTo18Decimals(1), '0x')) .to.emit(pool, 'Burn') - .withArgs(wallet.address, 0, 120, expandTo18Decimals(1), 0, '6017734268818165') + .withArgs(wallet.address, 0, 120, expandTo18Decimals(1), 0, '6017734268818165', 0) .to.not.emit(token0, 'Transfer') .to.not.emit(token1, 'Transfer'); await expect(pool.collect(wallet.address, 0, 120, MaxUint128, MaxUint128)) @@ -1185,7 +1186,7 @@ describe('AlgebraPool', () => { await swapExact0For1(expandTo18Decimals(2), other.address); await expect(pool.burn(-120, 0, expandTo18Decimals(1), '0x')) .to.emit(pool, 'Burn') - .withArgs(wallet.address, -120, 0, expandTo18Decimals(1), '6017734268818165', 0) + .withArgs(wallet.address, -120, 0, expandTo18Decimals(1),'6017734268818165', 0, 0) .to.not.emit(token0, 'Transfer') .to.not.emit(token1, 'Transfer'); await expect(pool.collect(wallet.address, -120, 0, MaxUint128, MaxUint128)) @@ -2054,7 +2055,7 @@ describe('AlgebraPool', () => { await swapExact1For0(expandTo18Decimals(1), wallet.address); await expect(pool.burn(120000, 121200, liquidityAmount, '0x')) .to.emit(pool, 'Burn') - .withArgs(wallet.address, 120000, 121200, liquidityAmount, '30012388425661', '999499999999999999') + .withArgs(wallet.address, 120000, 121200, liquidityAmount, '30012388425661', '999499999999999999', 0) .to.not.emit(token0, 'Transfer') .to.not.emit(token1, 'Transfer'); expect((await pool.globalState()).tick).to.eq(120197); @@ -2065,7 +2066,7 @@ describe('AlgebraPool', () => { await swapExact0For1(expandTo18Decimals(1), wallet.address); await expect(pool.burn(-121200, -120000, liquidityAmount, '0x')) .to.emit(pool, 'Burn') - .withArgs(wallet.address, -121200, -120000, liquidityAmount, '999499999999999999', '30012388425661') + .withArgs(wallet.address, -121200, -120000, liquidityAmount, '999499999999999999', '30012388425661', 0) .to.not.emit(token0, 'Transfer') .to.not.emit(token1, 'Transfer'); expect((await pool.globalState()).tick).to.eq(-120198); @@ -2430,6 +2431,187 @@ describe('AlgebraPool', () => { }); }); + describe('#pluginFee', () => { + let poolPlugin : MockPoolPlugin; + + beforeEach('initialize the pool', async () => { + const MockPoolPluginFactory = await ethers.getContractFactory('MockPoolPlugin'); + poolPlugin = (await MockPoolPluginFactory.deploy(await pool.getAddress())) as any as MockPoolPlugin; + await pool.setPlugin(poolPlugin); + await pool.setPluginConfig(255); + await pool.initialize(encodePriceSqrt(1, 1)); + await mint(wallet.address, minTick, maxTick, expandTo18Decimals(1)); + }); + + it('swap/burn fails if plugin fee exceeds max value', async () => { + await poolPlugin.setPluginFees(4000, 1000000); + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'incorrectPluginFee'); + await expect(pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x')).to.be.revertedWithCustomError(pool, 'incorrectPluginFee'); + }) + + it('swap fails if fees sum exceeds max value', async () => { + await poolPlugin.setPluginFees(15000, 990000); + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'incorrectPluginFee'); + await poolPlugin.setPluginFees(0, 990000); + await pool.setPluginConfig(1) + await pool.setFee(15000) + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'incorrectPluginFee'); + }) + + it('swap fails if plugin return incorrect selector', async () => { + await poolPlugin.disablePluginFeeHandle(); + await poolPlugin.setPluginFees(5000, 4000); + const selector = poolPlugin.interface.getFunction('handlePluginFee').selector; + + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)). + to.be.revertedWithCustomError(pool, 'invalidHookResponse').withArgs(selector); + }) + + it('works correct on swap', async () => { + await poolPlugin.setPluginFees(5000, 4000); + await swapExact0For1(expandTo18Decimals(1), wallet.address); + await swapExact0For1(expandTo18Decimals(1), wallet.address); + await swapExact1For0(expandTo18Decimals(1), wallet.address); + let pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[0]).to.be.eq(4n * 10n**15n); + expect(pluginFees[1]).to.be.eq(4n * 10n**15n) + }) + + it('works correct on swap, fee is 50%, 75%, 99%', async () => { + await poolPlugin.setPluginFees(0, 500000); + await swapExact0For1(expandTo18Decimals(1), wallet.address); + await swapExact1For0(expandTo18Decimals(1), wallet.address); + let pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)/2n); + + await poolPlugin.setPluginFees(0, 750000); + await swapExact1For0(expandTo18Decimals(1), wallet.address); + pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 125n / 100n); + + await poolPlugin.setPluginFees(0, 990000); + await swapExact1For0(expandTo18Decimals(1), wallet.address); + pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 224n / 100n); + }) + + it('works correct on burn', async () => { + await poolPlugin.setPluginFees(0, 6000); + const pluginBalance0Before = await token0.balanceOf(poolPlugin); + const pluginBalance1Before = await token1.balanceOf(poolPlugin); + await pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x') + const pluginBalance0After = await token0.balanceOf(poolPlugin); + const pluginBalance1After = await token1.balanceOf(poolPlugin); + expect(pluginBalance0After - pluginBalance0Before).to.be.eq(6n * 10n**15n-1n); + expect(pluginBalance1After - pluginBalance1Before).to.be.eq(6n * 10n**15n-1n); + }) + + it('works correct on burn single-sided position', async () => { + await poolPlugin.setPluginFees(0, 6000); + await mint(wallet.address, -120, -60, expandTo18Decimals(1)); + await mint(wallet.address, 60, 120, expandTo18Decimals(1)); + await pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x') + await pool.burn(-120, -60, expandTo18Decimals(1), '0x') + await pool.burn(60, 120, expandTo18Decimals(1), '0x') + let res = await pool.getPluginFeePending() + expect(res[0]).to.be.eq(17918296827593n); + expect(res[1]).to.be.eq(17918296827593n); + }) + + it('fees transfered to plugin', async () => { + await poolPlugin.setPluginFees(5000, 4000); + const pluginBalance0Before = await token0.balanceOf(poolPlugin); + const pluginBalance1Before = await token1.balanceOf(poolPlugin); + await swapExact0For1(expandTo18Decimals(1), wallet.address) + const pluginBalance0After = await token0.balanceOf(poolPlugin); + const pluginBalance1After = await token1.balanceOf(poolPlugin); + expect(pluginBalance0After - pluginBalance0Before).to.be.eq(4n * 10n**15n); + expect(pluginBalance1After - pluginBalance1Before).to.be.eq(0); + }) + + it('fees transfered to plugin, if comm fee is zero', async () => { + await poolPlugin.setPluginFees(5000, 4000); + await swapExact1For0(expandTo18Decimals(1), wallet.address) + const pluginBalance0Before = await token0.balanceOf(poolPlugin); + const pluginBalance1Before = await token1.balanceOf(poolPlugin); + await pool.advanceTime(86400); + await swapExact0For1(expandTo18Decimals(1), wallet.address) + const pluginBalance0After = await token0.balanceOf(poolPlugin); + const pluginBalance1After = await token1.balanceOf(poolPlugin); + expect(pluginBalance0After - pluginBalance0Before).to.be.eq(4n * 10n**15n); + expect(pluginBalance1After - pluginBalance1Before).to.be.eq(0); + }) + + it('fees transfered to plugin, after disable plugin fee and enable comm fee', async () => { + await poolPlugin.setPluginFees(5000, 4000); + await swapExact1For0(expandTo18Decimals(1), wallet.address) + await swapExact0For1(expandTo18Decimals(1), wallet.address) + const pluginBalance0Before = await token0.balanceOf(poolPlugin); + const pluginBalance1Before = await token1.balanceOf(poolPlugin); + await pool.advanceTime(86400); + await poolPlugin.setPluginFees(5000, 0); + await pool.setCommunityFee(100); + await swapExact0For1(expandTo18Decimals(1), wallet.address) + const pluginBalance0After = await token0.balanceOf(poolPlugin); + const pluginBalance1After = await token1.balanceOf(poolPlugin); + expect(pluginBalance0After - pluginBalance0Before).to.be.eq(4n * 10n**15n); + expect(pluginBalance1After - pluginBalance1Before).to.be.eq(0); + }) + + it('fees transfered to vault, after disable comm fee and enable plugin fee', async () => { + await pool.setCommunityFee(100); + await swapExact1For0(expandTo18Decimals(1), wallet.address) + await swapExact0For1(expandTo18Decimals(1), wallet.address) + const vaultBalance0Before = await token0.balanceOf(vaultAddress); + const vaultBalance1Before = await token1.balanceOf(vaultAddress); + await pool.advanceTime(86400); + await poolPlugin.setPluginFees(5000, 4000); + await pool.setCommunityFee(0); + await swapExact0For1(expandTo18Decimals(1), wallet.address) + const vaultBalance0After = await token0.balanceOf(vaultAddress); + const vaultBalance1After = await token1.balanceOf(vaultAddress); + expect(vaultBalance0After - vaultBalance0Before).to.be.eq(5n * 10n**13n); + expect(vaultBalance1After - vaultBalance1Before).to.be.eq(0); + }) + + it('works correct with communityFee', async () => { + await poolPlugin.setPluginFees(1000, 4000); + await pool.setCommunityFee(500); + await swapExact0For1(expandTo18Decimals(1), wallet.address); + await swapExact0For1(expandTo18Decimals(1), wallet.address); + const communityFees = await pool.getCommunityFeePending(); + const pluginFees = await pool.getPluginFeePending(); + + expect(communityFees[0]).to.be.eq(expandTo18Decimals(1) * 5n / 10000n); // 0.05% + expect(pluginFees[0]).to.be.eq(4n * 10n**15n); + }) + + it('emits an event with plugin fee and override fee on swap', async () => { + await poolPlugin.setPluginFees(4000, 6000); + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.emit(pool, 'Swap').withArgs( + await swapTarget.getAddress(), + wallet.address, + 10n**18n, + -497487437185929648n, + 39813146992092631956554748913n, + 1000000000000000000n, + -13764, + 4000, + 6000 + ) + }) + + it('emits an event with plugin fee and override fee on burn', async () => { + await poolPlugin.setPluginFees(4000, 6000); + await mint(wallet.address, 60, 120, expandTo18Decimals(1)); + await expect(pool.burn(60, 120, expandTo18Decimals(1), '0x')) + .to.emit(pool, 'Burn') + .withArgs(wallet.address, 60, 120, expandTo18Decimals(1), '2968464507771288', 0, 6000) + + }) + + }) + describe('PermissionedActions', async () => { describe('#setCommunityFee', () => { beforeEach('initialize the pool', async () => { @@ -2798,6 +2980,25 @@ describe('AlgebraPool', () => { await pool.setPluginConfig(223); await expect(flash(100, 200, other.address)).not.to.be.emit(poolPlugin, 'AfterFlash'); }); + + it('before/after swap hook is not called if caller is a plugin,', async () => { + await pool.initialize(encodePriceSqrt(1, 1)); + await mint(wallet.address, minTick, maxTick, expandTo18Decimals(1)); + await token0.transfer(poolPlugin, expandTo18Decimals(1)) + await token1.transfer(poolPlugin, expandTo18Decimals(1)) + await expect(poolPlugin.swap()).not.to.be.emit(poolPlugin, 'BeforeSwap'); + await expect(poolPlugin.swap()).not.to.be.emit(poolPlugin, 'AfterSwap'); + }); + + it('before/after modify hook is not called if caller is a plugin,', async () => { + await pool.initialize(encodePriceSqrt(1, 1)); + await mint(wallet.address, minTick, maxTick, expandTo18Decimals(1)); + await token0.transfer(poolPlugin, expandTo18Decimals(1)) + await token1.transfer(poolPlugin, expandTo18Decimals(1)) + await expect(poolPlugin.mint()).not.to.be.emit(poolPlugin, 'BeforeModifyPosition'); + await expect(poolPlugin.mint()).not.to.be.emit(poolPlugin, 'AfterModifyPosition'); + }); + }); describe('#setPlugin', () => { diff --git a/src/core/test/AlgebraPool.swaps.spec.ts b/src/core/test/AlgebraPool.swaps.spec.ts index 709926733..d4121e3ad 100644 --- a/src/core/test/AlgebraPool.swaps.spec.ts +++ b/src/core/test/AlgebraPool.swaps.spec.ts @@ -701,7 +701,9 @@ describe('AlgebraPool swap tests', () => { poolBalance1Delta, globalStateAfter.price, liquidityAfter, - globalStateAfter.tick + globalStateAfter.tick, + 0, + 0 ); const executionPrice = new Decimal(poolBalance1Delta.toString()).div(poolBalance0Delta.toString()).mul(-1); diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 1e2b36314..5ba6df9f9 100644 --- a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4833685`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4752295`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4833685`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4752295`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4820932`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4740098`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4820932`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4740098`; -exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; +exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10699`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22844`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22435`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index d8f911204..c7f4f1b7e 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,178 +4,190 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] #setFee by owner 1`] = `354 exports[`AlgebraPool gas tests [ @skip-on-coverage ] #setFee by plugin 1`] = `29954`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `115300`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `117308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115300`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `117308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price entire position burn but other positions are using the ticks 1`] = `108651`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price entire position burn but other positions are using the ticks 1`] = `111161`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113451`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `115961`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125180`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `127188`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125180`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `127188`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price entire position burn but other positions are using the ticks 1`] = `113059`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price entire position burn but other positions are using the ticks 1`] = `115569`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `117859`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `120369`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `126744`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `126744`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price entire position burn but other positions are using the ticks 1`] = `109313`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price entire position burn but other positions are using the ticks 1`] = `111822`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114113`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `116622`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case 1`] = `52569`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case 1`] = `52641`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case, two tokens 1`] = `70336`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case, two tokens 1`] = `70408`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `126330`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `128577`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `126330`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `128577`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `280193`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `282440`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `143430`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `145677`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `151483`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `153730`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `151483`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `153730`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `358391`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `360638`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `168583`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `170830`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `126899`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `129145`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `126899`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `129145`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `354539`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `356785`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `143999`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `146245`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61505`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `63735`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `102786`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103911`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `102755`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103880`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `118479`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119656`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `151837`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `153170`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `102924`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `104049`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `151837`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `153170`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171037`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `172370`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `102786`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103911`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `102750`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103875`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119303`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `120480`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `152688`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `154021`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171037`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `172370`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `102624`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103749`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `102620`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103745`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `102847`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103972`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `102795`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103920`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `102811`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103936`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115300`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 with plugin fee on first swap in block moves tick, no initialized crossings, with transfer 1`] = `158006`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115300`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 with plugin fee on first swap in block with no tick movement, with transfer 1`] = `157954`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price entire position burn but other positions are using the ticks 1`] = `108651`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 with plugin fee on first swap in block with no tick movement, without transfer 1`] = `132494`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113451`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125180`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `117308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125180`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price entire position burn but other positions are using the ticks 1`] = `111161`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price entire position burn but other positions are using the ticks 1`] = `113059`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `115961`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `117859`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `127188`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `127188`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price entire position burn but other positions are using the ticks 1`] = `115569`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price entire position burn but other positions are using the ticks 1`] = `109313`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `120369`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114113`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `126744`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case 1`] = `52569`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `126744`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case, two tokens 1`] = `70336`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price entire position burn but other positions are using the ticks 1`] = `111822`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `126330`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `116622`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `126330`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case 1`] = `52641`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `280193`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case, two tokens 1`] = `70408`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `143430`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `128577`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `151483`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `128577`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `151483`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `282440`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `358391`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `145677`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `168583`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `153730`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `126899`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `153730`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `126899`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `360638`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `354539`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `170830`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `143999`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `129145`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61505`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `129145`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `110609`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `356785`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `102992`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `146245`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `126539`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `63735`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `160608`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `112710`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `110747`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `112679`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `160608`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `128692`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `179808`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `162917`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `110609`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `112848`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `102987`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `162917`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `127363`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `182117`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `161459`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `112710`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `179808`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `104112`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `102861`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129516`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `102857`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `163768`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `110670`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `182117`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103032`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103986`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103048`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103982`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `112782`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `112730`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `104173`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 with plugin fee on first swap in block moves tick, no initialized crossings, with transfer 1`] = `169321`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 with plugin fee on first swap in block with no tick movement, with transfer 1`] = `169269`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 with plugin fee on first swap in block with no tick movement, without transfer 1`] = `136199`; diff --git a/src/core/test/__snapshots__/PriceMovement.spec.ts.snap b/src/core/test/__snapshots__/PriceMovement.spec.ts.snap index 1310265c7..772f13231 100644 --- a/src/core/test/__snapshots__/PriceMovement.spec.ts.snap +++ b/src/core/test/__snapshots__/PriceMovement.spec.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact in capped 1`] = `1665`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact in capped 1`] = `1656`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact in partial 1`] = `2503`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact in partial 1`] = `2497`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact out capped 1`] = `1489`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact out capped 1`] = `1486`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact out partial 1`] = `2503`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap one for zero exact out partial 1`] = `2497`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact in capped 1`] = `1666`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact in capped 1`] = `1657`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact in partial 1`] = `2645`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact in partial 1`] = `2639`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact out capped 1`] = `1490`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact out capped 1`] = `1487`; -exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact out partial 1`] = `2645`; +exports[`PriceMovementMath #movePriceTowardsTarget gas [ @skip-on-coverage ] swap zero for one exact out partial 1`] = `2639`; diff --git a/src/farming/.solcover.js b/src/farming/.solcover.js index 53c8df822..7c8f6101a 100644 --- a/src/farming/.solcover.js +++ b/src/farming/.solcover.js @@ -5,6 +5,7 @@ const skipFiles = testContracts.map((x) => "test/" + x) module.exports = { skipFiles: skipFiles, + configureYulOptimizer: true, mocha: { grep: '@skip-on-coverage', // Find everything with this tag invert: true, // Run the grep's inverse set. diff --git a/src/farming/contracts/FarmingCenter.sol b/src/farming/contracts/FarmingCenter.sol index b704a964e..4c8634b7f 100644 --- a/src/farming/contracts/FarmingCenter.sol +++ b/src/farming/contracts/FarmingCenter.sol @@ -12,7 +12,7 @@ import '@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IFarmin import './interfaces/IFarmingCenter.sol'; import './libraries/IncentiveId.sol'; -/// @title Algebra Integral 1.1 main farming contract +/// @title Algebra Integral 1.2 main farming contract /// @dev Manages farmings and performs entry, exit and other actions. contract FarmingCenter is IFarmingCenter, IPositionFollower, Multicall { /// @inheritdoc IFarmingCenter @@ -139,7 +139,7 @@ contract FarmingCenter is IFarmingCenter, IPositionFollower, Multicall { function _checkParamsForVirtualPoolToggle(address virtualPool, IFarmingPlugin plugin) internal view returns (IAlgebraPool pool) { require(msg.sender == address(eternalFarming), 'Only farming can call this'); require(virtualPool != address(0), 'Zero address as virtual pool'); - pool = IAlgebraPool(plugin.pool()); + pool = IAlgebraPool(plugin.getPool()); require( address(pool) == PoolAddress.computeAddress(algebraPoolDeployer, PoolAddress.PoolKey(address(0), pool.token0(), pool.token1())), 'Invalid pool' diff --git a/src/farming/contracts/farmings/AlgebraEternalFarming.sol b/src/farming/contracts/farmings/AlgebraEternalFarming.sol index 0a098fe6d..284d730e7 100644 --- a/src/farming/contracts/farmings/AlgebraEternalFarming.sol +++ b/src/farming/contracts/farmings/AlgebraEternalFarming.sol @@ -23,7 +23,7 @@ import '../libraries/NFTPositionInfo.sol'; import './EternalVirtualPool.sol'; -/// @title Algebra Integral 1.1 eternal (v2-like) farming +/// @title Algebra Integral 1.2 eternal (v2-like) farming /// @notice Manages rewards and virtual pools contract AlgebraEternalFarming is IAlgebraEternalFarming { using SafeCast for int256; diff --git a/src/farming/contracts/farmings/EternalVirtualPool.sol b/src/farming/contracts/farmings/EternalVirtualPool.sol index 0b0efdf7c..9e5c7b561 100644 --- a/src/farming/contracts/farmings/EternalVirtualPool.sol +++ b/src/farming/contracts/farmings/EternalVirtualPool.sol @@ -12,7 +12,7 @@ import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolError import '../base/VirtualTickStructure.sol'; -/// @title Algebra Integral 1.1 eternal virtual pool +/// @title Algebra Integral 1.2 eternal virtual pool /// @notice used to track active liquidity in farming and distribute rewards contract EternalVirtualPool is Timestamp, VirtualTickStructure { using TickManagement for mapping(int24 => TickManagement.Tick); diff --git a/src/farming/package-lock.json b/src/farming/package-lock.json index 70589eed9..342dda438 100644 --- a/src/farming/package-lock.json +++ b/src/farming/package-lock.json @@ -1,17 +1,14 @@ { "name": "@cryptoalgebra/integral-farming", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cryptoalgebra/integral-farming", - "version": "1.1.0", + "version": "1.2.0", "license": "GPL-3.0-or-later", "dependencies": { - "@cryptoalgebra/integral-base-plugin": "1.1.0", - "@cryptoalgebra/integral-core": "1.1.0", - "@cryptoalgebra/integral-periphery": "1.1.0", "@openzeppelin/contracts": "4.9.3" }, "devDependencies": { @@ -23,45 +20,6 @@ "npm": ">=8.0.0" } }, - "node_modules/@cryptoalgebra/integral-base-plugin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-base-plugin/-/integral-base-plugin-1.1.0.tgz", - "integrity": "sha512-fWpdTjIf1VFLB+qQSU9TP7BdgWrRjlxIBVYEN4I8BtfIIwr8Ay3QrYYsUvI2FFdN2I5HroZLanmgYS0+IjXLDg==", - "dependencies": { - "@cryptoalgebra/integral-core": "1.1.0", - "@cryptoalgebra/integral-periphery": "1.1.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@cryptoalgebra/integral-core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.1.0.tgz", - "integrity": "sha512-SESM8dIrNd2vkLsiYDBZxewmsz4ZyhfuVIe2SNTCCayl14W2dUbZ1y4qWRqSZ6JPKIFOHdrdLoNk+fFIC7Ukvg==", - "dependencies": { - "@openzeppelin/contracts": "4.9.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@cryptoalgebra/integral-periphery": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-periphery/-/integral-periphery-1.1.0.tgz", - "integrity": "sha512-Z+/FVtucH2GeoNuMcf+3Z859fNIOBNJTHSynu+UVV9NtMia9c7olEzcaPpyFkE7kynQgUcc+KffgE7KFDS0H+Q==", - "dependencies": { - "@cryptoalgebra/integral-core": "1.1.0", - "@openzeppelin/contracts": "4.9.3", - "@uniswap/v2-core": "1.0.1" - }, - "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", @@ -73,14 +31,6 @@ "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", "dev": true }, - "node_modules/@uniswap/v2-core": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", - "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==", - "engines": { - "node": ">=10" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", diff --git a/src/farming/package.json b/src/farming/package.json index 149189a0d..a111a7427 100644 --- a/src/farming/package.json +++ b/src/farming/package.json @@ -2,7 +2,7 @@ "name": "@cryptoalgebra/integral-farming", "description": "Liquidity mining contracts for Algebra Integral protocol", "license": "GPL-3.0-or-later", - "version": "1.1.0", + "version": "1.2.0", "publishConfig": { "access": "public" }, @@ -24,9 +24,9 @@ ], "dependencies": { "@openzeppelin/contracts": "4.9.3", - "@cryptoalgebra/integral-core": "1.1.0", - "@cryptoalgebra/integral-periphery": "1.1.0", - "@cryptoalgebra/integral-base-plugin": "1.1.0" + "@cryptoalgebra/integral-core": "1.2.0", + "@cryptoalgebra/integral-periphery": "1.2.0", + "@cryptoalgebra/integral-base-plugin": "1.2.0" }, "devDependencies": { "@types/lodash": "^4.14.170", diff --git a/src/farming/scripts/deploy.js b/src/farming/scripts/deploy.js index 3c897b6c8..e0b2623b6 100644 --- a/src/farming/scripts/deploy.js +++ b/src/farming/scripts/deploy.js @@ -1,7 +1,7 @@ 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'); +const BasePluginV2FactoryComplied = require('@cryptoalgebra/integral-base-plugin/artifacts/contracts/BasePluginV2Factory.sol/BasePluginV2Factory.json'); async function main() { const deployDataPath = path.resolve(__dirname, '../../../deploys.json') @@ -26,7 +26,7 @@ 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) + const pluginFactory = await hre.ethers.getContractAt(BasePluginV2FactoryComplied.abi, deploysData.BasePluginV2Factory) await (await pluginFactory.setFarmingAddress(FarmingCenter.target)).wait() console.log('Updated farming center address in plugin factory') diff --git a/src/farming/test/AlgebraFarming.spec.ts b/src/farming/test/AlgebraFarming.spec.ts index 81d937764..7c54bfc2b 100644 --- a/src/farming/test/AlgebraFarming.spec.ts +++ b/src/farming/test/AlgebraFarming.spec.ts @@ -271,7 +271,7 @@ describe('AlgebraFarming', () => { }) ) ); - await time.setNextBlockTimestamp(startTime + 1); + await time.setNextBlockTimestamp(startTime + 100); const trader = actors.traderUser0(); await helpers.makeTickGoFlow({ trader, @@ -300,7 +300,7 @@ describe('AlgebraFarming', () => { const { helpers, createIncentiveResult } = subject; - await time.increaseTo(endTime + 1); + await time.increaseTo(endTime + 100); const trader = actors.traderUser0(); await helpers.makeTickGoFlow({ @@ -341,7 +341,7 @@ describe('AlgebraFarming', () => { const startTime = epoch + 1_000; const endTime = startTime + duration; - await time.increaseTo(endTime + 1); + await time.increaseTo(endTime + 100); const trader = actors.traderUser0(); await helpers.makeTickGoFlow({ @@ -407,7 +407,7 @@ describe('AlgebraFarming', () => { deadline: (await blockTimestamp()) + 1000, }); - await time.setNextBlockTimestamp(endTime + 1); + await time.setNextBlockTimestamp(endTime + 100); const trader = actors.traderUser0(); await helpers.makeTickGoFlow({ diff --git a/src/farming/test/helpers/index.ts b/src/farming/test/helpers/index.ts index 4e2cb9c6b..f4e7a09b4 100644 --- a/src/farming/test/helpers/index.ts +++ b/src/farming/test/helpers/index.ts @@ -125,9 +125,9 @@ export class HelperCommands { }, pluginAddres ); - // @ts-ignore - virtualPoolAddress = (await txResult.wait(1)).logs[4].args['virtualPool']; + // @ts-ignore + virtualPoolAddress = (await txResult.wait()).logs[3].args['virtualPool']; return { ..._.pick(params, ['poolAddress', 'totalReward', 'bonusReward', 'rewardToken', 'bonusRewardToken']), nonce, diff --git a/src/farming/test/shared/fixtures.ts b/src/farming/test/shared/fixtures.ts index 008bd967a..3fdc27682 100644 --- a/src/farming/test/shared/fixtures.ts +++ b/src/farming/test/shared/fixtures.ts @@ -273,9 +273,9 @@ export const algebraFixture: () => Promise = async () => { const fee = FeeAmount.MEDIUM; - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); + 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)); + await nft.createAndInitializePoolIfNecessary(tokens[1], tokens[2], ZERO_ADDRESS, encodePriceSqrt(1, 1),'0x'); const pool01 = await factory.poolByPair(tokens[0], tokens[1]); diff --git a/src/farming/test/unit/EternalFarms.spec.ts b/src/farming/test/unit/EternalFarms.spec.ts index 6f337cdf6..d0eb4939c 100644 --- a/src/farming/test/unit/EternalFarms.spec.ts +++ b/src/farming/test/unit/EternalFarms.spec.ts @@ -1270,7 +1270,7 @@ describe('unit/EternalFarms', () => { bonusRewardRate: 50n, }); - await Time.setAndMine(timestamps.startTime + 1); + await Time.setAndMine(timestamps.startTime + 100); const mintResult = await helpers.mintDepositFarmFlow({ lp: lpUser0, @@ -1412,7 +1412,7 @@ describe('unit/EternalFarms', () => { bonusRewardRate: 50n, }); - await Time.setAndMine(timestamps.startTime + 1); + await Time.setAndMine(timestamps.startTime + 100); const mintResult = await helpers.mintDepositFarmFlow({ lp: lpUser0, @@ -1445,10 +1445,24 @@ describe('unit/EternalFarms', () => { }); 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: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), + rewardToken: rewardTokenAddress, + bonusRewardToken: bonusRewardTokenAddress, pool: context.pool01, nonce: localNonce, }, @@ -1456,13 +1470,13 @@ describe('unit/EternalFarms', () => { lpUser0.address ); - const rewardTokenBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - const bonusRewardTokenBalanceBefore = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + 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: await context.rewardToken.getAddress(), - bonusRewardToken: await context.bonusRewardToken.getAddress(), + rewardToken: rewardTokenAddress, + bonusRewardToken: bonusRewardTokenAddress, pool: context.pool01, nonce: localNonce, }, @@ -1470,8 +1484,9 @@ describe('unit/EternalFarms', () => { lpUser0.address ); - const rewardTokenBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.rewardToken); - const bonusRewardTokenBalanceAfter = await context.eternalFarming.rewards(lpUser0.address, context.bonusRewardToken); + 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); @@ -1594,7 +1609,7 @@ describe('unit/EternalFarms', () => { deadline: (await blockTimestamp()) + 10000, }); - await Time.setAndMine(timestamps.startTime + 1); + 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); @@ -1643,7 +1658,7 @@ describe('unit/EternalFarms', () => { await context.rewardToken.getAddress(), await context.bonusRewardToken.getAddress(), lpUser0.address, - 9999n, + 9079n, 199n ); }); @@ -1898,7 +1913,7 @@ describe('unit/EternalFarms', () => { await erc20Helper.ensureBalancesAndApprovals(lpUser0, [token0, token1], amountDesired, await context.nft.getAddress()); - await context.nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await context.nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); const poolAddress = await context.factory.poolByPair(token0, token1); diff --git a/src/farming/test/unit/FarmingCenter.spec.ts b/src/farming/test/unit/FarmingCenter.spec.ts index 9b77a2c9e..c1542518a 100644 --- a/src/farming/test/unit/FarmingCenter.spec.ts +++ b/src/farming/test/unit/FarmingCenter.spec.ts @@ -190,7 +190,7 @@ describe('unit/FarmingCenter', () => { bonusRewardRate: 50n, }); - await Time.setAndMine(timestamps.startTime + 1); + await Time.setAndMine(timestamps.startTime + 100); const mintResultEternal = await helpers.mintDepositFarmFlow({ lp: lpUser0, @@ -489,7 +489,7 @@ describe('unit/FarmingCenter', () => { bonusRewardRate: 50n, }); - await Time.setAndMine(timestamps.startTime + 1); + await Time.setAndMine(timestamps.startTime + 100); const mintResultEternal = await helpers.mintDepositFarmFlow({ lp: lpUser0, @@ -572,11 +572,11 @@ describe('unit/FarmingCenter', () => { 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(199699n); - expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(99549n); + expect(balanceAfter - balanceBefore).to.equal(189799); + expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(94599); - await claimAndCheck(context.rewardToken, lpUser0, 199699n); - await claimAndCheck(context.bonusRewardToken, lpUser0, 99549n); + await claimAndCheck(context.rewardToken, lpUser0, 189799n); + await claimAndCheck(context.bonusRewardToken, lpUser0, 94599n); }); it('collect rewards after eternalFarming deactivate', async () => { @@ -631,11 +631,11 @@ describe('unit/FarmingCenter', () => { 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(199699n); - expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(99549n); + expect(balanceAfter - balanceBefore).to.equal(189799); + expect(bonusBalanceAfter - bonusBalanceBefore).to.equal(94599); - await claimAndCheck(context.rewardToken, lpUser0, 199699n); - await claimAndCheck(context.bonusRewardToken, lpUser0, 99549n); + await claimAndCheck(context.rewardToken, lpUser0, 189799n); + await claimAndCheck(context.bonusRewardToken, lpUser0, 94599n); }); it('cannot collect if not owner', async () => { diff --git a/src/farming/test/unit/Multicall.spec.ts b/src/farming/test/unit/Multicall.spec.ts index 3f9f4f8b8..a18652981 100644 --- a/src/farming/test/unit/Multicall.spec.ts +++ b/src/farming/test/unit/Multicall.spec.ts @@ -42,8 +42,8 @@ describe('unit/Multicall', () => { multicallFixture = async () => { const context = await algebraFixture(); - const helpers = HelperCommands.fromTestContext(context, actors, provider); + const helpers = HelperCommands.fromTestContext(context, actors, provider); await erc20Helper.ensureBalancesAndApprovals(multicaller, [context.token0, context.token1], amountDesired, await context.nft.getAddress()); const mintResult = await helpers.mintFlow({ @@ -66,7 +66,6 @@ describe('unit/Multicall', () => { totalReward, await context.eternalFarming.getAddress() ); - await helpers.createIncentiveFlow({ rewardToken: context.rewardToken, bonusRewardToken: context.bonusRewardToken, @@ -77,7 +76,6 @@ describe('unit/Multicall', () => { rewardRate: 10n, bonusRewardRate: 50n, }); - await context.nft.connect(multicaller).approveForFarming(tokenId, true, context.farmingCenter); await context.farmingCenter.connect(multicaller).enterFarming(farmIncentiveKey, tokenId); diff --git a/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap b/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap index 50df3ca84..38bb67390 100644 --- a/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap +++ b/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap @@ -2,6 +2,6 @@ exports[`unit/EternalFarms #claimReward when requesting the full amount has gas cost [ @skip-on-coverage ] 1`] = `60772`; -exports[`unit/EternalFarms #enterFarming works and has gas cost [ @skip-on-coverage ] 1`] = `498891`; +exports[`unit/EternalFarms #enterFarming works and has gas cost [ @skip-on-coverage ] 1`] = `499494`; -exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `171745`; +exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `177134`; diff --git a/src/periphery/contracts/AlgebraCustomPoolEntryPoint.sol b/src/periphery/contracts/AlgebraCustomPoolEntryPoint.sol index b7a4c4073..d595e8a8e 100644 --- a/src/periphery/contracts/AlgebraCustomPoolEntryPoint.sol +++ b/src/periphery/contracts/AlgebraCustomPoolEntryPoint.sol @@ -7,7 +7,7 @@ import {IAlgebraFactory} from '@cryptoalgebra/integral-core/contracts/interfaces /// @title Algebra custom pool entry point /// @notice Is used to create custom pools -/// @dev Version: Algebra Integral 2.0 +/// @dev Version: Algebra Integral 1.2 contract AlgebraCustomPoolEntryPoint is IAlgebraCustomPoolEntryPoint { /// @inheritdoc IAlgebraCustomPoolEntryPoint address public immutable override factory; diff --git a/src/periphery/contracts/NonfungiblePositionManager.sol b/src/periphery/contracts/NonfungiblePositionManager.sol index 7e1de9349..407a1278a 100644 --- a/src/periphery/contracts/NonfungiblePositionManager.sol +++ b/src/periphery/contracts/NonfungiblePositionManager.sol @@ -19,7 +19,7 @@ import './base/PeripheryValidation.sol'; import './base/SelfPermit.sol'; import './base/PoolInitializer.sol'; -/// @title Algebra Integral 1.1 NFT positions +/// @title Algebra Integral 1.2 NFT positions /// @notice Wraps Algebra positions in the ERC721 non-fungible token interface /// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: /// https://github.com/Uniswap/v3-periphery diff --git a/src/periphery/contracts/SwapRouter.sol b/src/periphery/contracts/SwapRouter.sol index 5bba6bdab..a6599a9be 100644 --- a/src/periphery/contracts/SwapRouter.sol +++ b/src/periphery/contracts/SwapRouter.sol @@ -15,7 +15,7 @@ import './libraries/Path.sol'; import './libraries/PoolAddress.sol'; import './libraries/CallbackValidation.sol'; -/// @title Algebra Integral 1.1 Swap Router +/// @title Algebra Integral 1.2 Swap Router /// @notice Router for stateless execution of swaps against Algebra /// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: /// https://github.com/Uniswap/v3-periphery diff --git a/src/periphery/contracts/base/PoolInitializer.sol b/src/periphery/contracts/base/PoolInitializer.sol index 377f4928b..6203e061f 100644 --- a/src/periphery/contracts/base/PoolInitializer.sol +++ b/src/periphery/contracts/base/PoolInitializer.sol @@ -19,7 +19,8 @@ abstract contract PoolInitializer is IPoolInitializer, PeripheryImmutableState { address token0, address token1, address deployer, - uint160 sqrtPriceX96 + uint160 sqrtPriceX96, + bytes calldata data ) external payable override returns (address pool) { require(token0 < token1, 'Invalid order of tokens'); @@ -32,7 +33,7 @@ abstract contract PoolInitializer is IPoolInitializer, PeripheryImmutableState { if (pool == address(0)) { if (deployer == address(0)) { - pool = _factory.createPool(token0, token1); + pool = _factory.createPool(token0, token1, data); _initializePool(pool, sqrtPriceX96); } diff --git a/src/periphery/contracts/interfaces/IPoolInitializer.sol b/src/periphery/contracts/interfaces/IPoolInitializer.sol index a8f8aac5b..8fa9bc332 100644 --- a/src/periphery/contracts/interfaces/IPoolInitializer.sol +++ b/src/periphery/contracts/interfaces/IPoolInitializer.sol @@ -13,11 +13,13 @@ interface IPoolInitializer { /// @param token0 The contract address of token0 of the pool /// @param token1 The contract address of token1 of the pool /// @param sqrtPriceX96 The initial square root price of the pool as a Q64.96 value + /// @param data Data for plugin initialization /// @return pool Returns the pool address based on the pair of tokens and fee, will return the newly created pool address if necessary function createAndInitializePoolIfNecessary( address token0, address token1, address deployer, - uint160 sqrtPriceX96 + uint160 sqrtPriceX96, + bytes calldata data ) external payable returns (address pool); } diff --git a/src/periphery/contracts/interfaces/IQuoterV2.sol b/src/periphery/contracts/interfaces/IQuoterV2.sol index 4782649ab..5c55f38a4 100644 --- a/src/periphery/contracts/interfaces/IQuoterV2.sol +++ b/src/periphery/contracts/interfaces/IQuoterV2.sol @@ -13,8 +13,8 @@ interface IQuoterV2 { /// @notice Returns the amount out received for a given exact input swap without executing the swap /// @param path The path of the swap, i.e. each token pair /// @param amountInRequired The desired amount of the first token to swap - /// @return amountOut The amount of the last token that would be received - /// @return amountIn The amount of the last token that should be paid + /// @return amountOutList The amount of the last token that would be received + /// @return amountInList The amount of the last token that should be paid /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path /// @return gasEstimate The estimate of the gas that the swap consumes @@ -25,8 +25,8 @@ interface IQuoterV2 { ) external returns ( - uint256 amountOut, - uint256 amountIn, + uint256[] memory amountOutList, + uint256[] memory amountInList, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate, @@ -69,8 +69,8 @@ interface IQuoterV2 { /// @notice Returns the amount in required for a given exact output swap without executing the swap /// @param path The path of the swap, i.e. each token pair. Path must be provided in reverse order /// @param amountOutRequired The amount of the last token to receive - /// @return amountOut The amount of the last token that would be received - /// @return amountIn The amount of first token required to be paid + /// @return amountOutList The amount of the last token that would be received + /// @return amountInList The amount of first token required to be paid /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path /// @return gasEstimate The estimate of the gas that the swap consumes @@ -81,8 +81,8 @@ interface IQuoterV2 { ) external returns ( - uint256 amountOut, - uint256 amountIn, + uint256[] memory amountOutList, + uint256[] memory amountInList, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate, diff --git a/src/periphery/contracts/lens/Quoter.sol b/src/periphery/contracts/lens/Quoter.sol index ade42f985..0526b2aa7 100644 --- a/src/periphery/contracts/lens/Quoter.sol +++ b/src/periphery/contracts/lens/Quoter.sol @@ -14,7 +14,7 @@ import '../libraries/Path.sol'; import '../libraries/PoolAddress.sol'; import '../libraries/CallbackValidation.sol'; -/// @title Algebra Integral 1.1 Quoter +/// @title Algebra Integral 1.2 Quoter /// @notice Allows getting the expected amount out or amount in for a given swap without executing the swap /// @dev These functions are not gas efficient and should _not_ be called on chain. Instead, optimistically execute /// the swap and check the amounts in the callback. diff --git a/src/periphery/contracts/lens/QuoterV2.sol b/src/periphery/contracts/lens/QuoterV2.sol index 8edf893ae..741739119 100644 --- a/src/periphery/contracts/lens/QuoterV2.sol +++ b/src/periphery/contracts/lens/QuoterV2.sol @@ -13,7 +13,7 @@ import '../libraries/PoolAddress.sol'; import '../libraries/CallbackValidation.sol'; import '../libraries/PoolTicksCounter.sol'; -/// @title Algebra Integral 1.1 QuoterV2 +/// @title Algebra Integral 1.2 QuoterV2 /// @notice Allows getting the expected amount out or amount in for a given swap without executing the swap /// @dev These functions are not gas efficient and should _not_ be called on chain. Instead, optimistically execute /// the swap and check the amounts in the callback. @@ -160,14 +160,16 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { public override returns ( - uint256 amountOut, - uint256 amountIn, + uint256[] memory amountOutList, + uint256[] memory amountInList, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate, uint16[] memory feeList ) { + amountOutList = new uint256[](path.numPools()); + amountInList = new uint256[](path.numPools()); sqrtPriceX96AfterList = new uint160[](path.numPools()); initializedTicksCrossedList = new uint32[](path.numPools()); feeList = new uint16[](path.numPools()); @@ -185,21 +187,17 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { } // the outputs of prior swaps become the inputs to subsequent ones - uint256 _amountOut; - uint256 _amountIn; uint256 _gasEstimate; ( - _amountOut, - _amountIn, + amountOutList[i], + amountInList[i], sqrtPriceX96AfterList[i], initializedTicksCrossedList[i], _gasEstimate, feeList[i] ) = quoteExactInputSingle(params); - if (i == 0) amountIn = _amountIn; - - amountInRequired = _amountOut; + amountInRequired = amountOutList[i]; gasEstimate += _gasEstimate; i++; @@ -208,8 +206,8 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { path = path.skipToken(); } else { return ( - amountInRequired, - amountIn, + amountOutList, + amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList, gasEstimate, @@ -264,14 +262,16 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { public override returns ( - uint256 amountOut, - uint256 amountIn, + uint256[] memory amountOutList, + uint256[] memory amountInList, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate, uint16[] memory feeList ) { + amountOutList = new uint256[](path.numPools()); + amountInList = new uint256[](path.numPools()); sqrtPriceX96AfterList = new uint160[](path.numPools()); initializedTicksCrossedList = new uint32[](path.numPools()); feeList = new uint16[](path.numPools()); @@ -289,21 +289,17 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { } // the inputs of prior swaps become the outputs of subsequent ones - uint256 _amountOut; - uint256 _amountIn; uint256 _gasEstimate; ( - _amountOut, - _amountIn, + amountOutList[i], + amountInList[i], sqrtPriceX96AfterList[i], initializedTicksCrossedList[i], _gasEstimate, feeList[i] ) = quoteExactOutputSingle(params); - if (i == 0) amountOut = _amountOut; - - amountOutRequired = _amountIn; + amountOutRequired = amountInList[i]; gasEstimate += _gasEstimate; i++; @@ -312,8 +308,8 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { path = path.skipToken(); } else { return ( - amountOut, - amountOutRequired, + amountOutList, + amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList, gasEstimate, diff --git a/src/periphery/contracts/lens/TickLens.sol b/src/periphery/contracts/lens/TickLens.sol index e048d928f..95fe98585 100644 --- a/src/periphery/contracts/lens/TickLens.sol +++ b/src/periphery/contracts/lens/TickLens.sol @@ -6,7 +6,7 @@ import '@cryptoalgebra/integral-core/contracts/libraries/TickTree.sol'; import '../interfaces/ITickLens.sol'; -/// @title Algebra Integral 1.1 Tick Lens contract +/// @title Algebra Integral 1.2 Tick Lens contract /// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: /// https://github.com/Uniswap/v3-periphery contract TickLens is ITickLens { diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 72dbf1610..fd6364b16 100644 --- a/src/periphery/contracts/libraries/PoolAddress.sol +++ b/src/periphery/contracts/libraries/PoolAddress.sol @@ -5,7 +5,7 @@ pragma solidity >=0.5.0; /// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: /// https://github.com/Uniswap/v3-periphery library PoolAddress { - bytes32 internal constant POOL_INIT_CODE_HASH = 0x4b9e4a8044ce5695e06fce9421a63b6f5c3db8a561eebb30ea4c775469e36eaf; + bytes32 internal constant POOL_INIT_CODE_HASH = 0xb3fc09be5eb433d99b1ec89fd8435aaf5ffea75c1879e19028aa2414a14b3c85; /// @notice The identifying key of the pool struct PoolKey { diff --git a/src/periphery/contracts/test/CustomPlugin.sol b/src/periphery/contracts/test/CustomPlugin.sol index 0b97b6921..c348492bc 100644 --- a/src/periphery/contracts/test/CustomPlugin.sol +++ b/src/periphery/contracts/test/CustomPlugin.sol @@ -42,9 +42,9 @@ contract CustomPlugin is Timestamp, IAlgebraPlugin { int24, int128, bytes calldata - ) external override returns (bytes4) { + ) external override returns (bytes4, uint24) { _updatePluginConfigInPool(); // should not be called, reset config - return IAlgebraPlugin.beforeModifyPosition.selector; + return (IAlgebraPlugin.beforeModifyPosition.selector, 0); } /// @dev unused @@ -70,9 +70,9 @@ contract CustomPlugin is Timestamp, IAlgebraPlugin { uint160, bool, bytes calldata - ) external override returns (bytes4) { + ) external override returns (bytes4, uint24, uint24) { IAlgebraPool(pool).setFee(10000); - return IAlgebraPlugin.beforeSwap.selector; + return (IAlgebraPlugin.beforeSwap.selector, 0, 0); } function afterSwap( @@ -89,6 +89,10 @@ contract CustomPlugin is Timestamp, IAlgebraPlugin { return IAlgebraPlugin.afterSwap.selector; } + function handlePluginFee(uint256, uint256) external pure returns (bytes4) { + return IAlgebraPlugin.handlePluginFee.selector; + } + /// @dev unused function beforeFlash(address, address, uint256, uint256, bytes calldata) external override returns (bytes4) { _updatePluginConfigInPool(); // should not be called, reset config diff --git a/src/periphery/contracts/test/MockPlugin.sol b/src/periphery/contracts/test/MockPlugin.sol index 48e1a1621..4b073c70c 100644 --- a/src/periphery/contracts/test/MockPlugin.sol +++ b/src/periphery/contracts/test/MockPlugin.sol @@ -23,10 +23,14 @@ contract MockPlugin is IAlgebraPlugin { int24, int128, bytes calldata - ) external pure returns (bytes4) { - return IAlgebraPlugin.beforeModifyPosition.selector; + ) external pure returns (bytes4, uint24) { + return (IAlgebraPlugin.beforeModifyPosition.selector, 0); } + function handlePluginFee(uint256, uint256) external pure returns (bytes4) { + return IAlgebraPlugin.handlePluginFee.selector; + } + function afterModifyPosition( address, address, @@ -40,8 +44,8 @@ contract MockPlugin is IAlgebraPlugin { return IAlgebraPlugin.afterModifyPosition.selector; } - function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external pure returns (bytes4) { - return IAlgebraPlugin.beforeSwap.selector; + function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external pure returns (bytes4, uint24, uint24) { + return (IAlgebraPlugin.beforeSwap.selector, 0, 0); } function afterSwap( diff --git a/src/periphery/package-lock.json b/src/periphery/package-lock.json index 0e3b96d55..aff7404a2 100644 --- a/src/periphery/package-lock.json +++ b/src/periphery/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cryptoalgebra/integral-periphery", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cryptoalgebra/integral-periphery", - "version": "1.1.0", + "version": "1.2.0", "license": "GPL-2.0-or-later", "dependencies": { "@openzeppelin/contracts": "4.9.3", diff --git a/src/periphery/package.json b/src/periphery/package.json index 4881786f9..781e1e7c9 100644 --- a/src/periphery/package.json +++ b/src/periphery/package.json @@ -5,7 +5,7 @@ "publishConfig": { "access": "public" }, - "version": "1.1.0", + "version": "1.2.0", "keywords": [ "algebra", "periphery" @@ -27,7 +27,7 @@ "dependencies": { "@openzeppelin/contracts": "4.9.3", "@uniswap/v2-core": "1.0.1", - "@cryptoalgebra/integral-core": "1.1.0" + "@cryptoalgebra/integral-core": "1.2.0" }, "devDependencies": { "is-svg": "^4.3.1" diff --git a/src/periphery/scripts/deploy.js b/src/periphery/scripts/deploy.js index 8560c63a4..02e3041fe 100644 --- a/src/periphery/scripts/deploy.js +++ b/src/periphery/scripts/deploy.js @@ -9,7 +9,7 @@ async function main() { let deploysData = JSON.parse(fs.readFileSync(deployDataPath, 'utf8')); // WNativeTokenAddress - const WNativeTokenAddress = '0x6E2542aFC68a1697FeB2810437DF9409D3b93493'; + const WNativeTokenAddress = '0x94373a4919b3240d86ea41593d5eba789fef3848'; const signers = await hre.ethers.getSigners(); const ProxyAdmin = signers[0].address; diff --git a/src/periphery/test/NonfungiblePositionManager.spec.ts b/src/periphery/test/NonfungiblePositionManager.spec.ts index 31d3d0dd2..e20768e67 100644 --- a/src/periphery/test/NonfungiblePositionManager.spec.ts +++ b/src/periphery/test/NonfungiblePositionManager.spec.ts @@ -86,13 +86,13 @@ describe('NonfungiblePositionManager', () => { ]); const code = await wallet.provider.getCode(expectedAddress); expect(code).to.eq('0x'); - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); const codeAfter = await wallet.provider.getCode(expectedAddress); expect(codeAfter).to.not.eq('0x'); }); it('is payable', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), { value: 1 }); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x', { value: 1 }); }); it('works if pool is created but not initialized', async () => { @@ -102,10 +102,10 @@ describe('NonfungiblePositionManager', () => { await tokens[0].getAddress(), await tokens[1].getAddress(), ]); - await factory.createPool(tokens[0], tokens[1]); + await factory.createPool(tokens[0], tokens[1], '0x'); const code = await wallet.provider.getCode(expectedAddress); expect(code).to.not.eq('0x'); - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(2, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(2, 1), '0x'); }); it('works if pool is created and initialized', async () => { @@ -113,7 +113,7 @@ describe('NonfungiblePositionManager', () => { await tokens[0].getAddress(), await tokens[1].getAddress(), ]); - await factory.createPool(tokens[0], tokens[1]); + await factory.createPool(tokens[0], tokens[1], '0x'); const pool = new ethers.Contract(expectedAddress, IAlgebraPoolABI, wallet); await pool.initialize(encodePriceSqrt(3, 1)); @@ -121,7 +121,7 @@ describe('NonfungiblePositionManager', () => { if (!wallet.provider) throw new Error('No provider'); const code = await wallet.provider.getCode(expectedAddress); expect(code).to.not.eq('0x'); - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(4, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(4, 1), '0x'); }); it('could theoretically use eth via multicall', async () => { @@ -129,14 +129,14 @@ describe('NonfungiblePositionManager', () => { const createAndInitializePoolIfNecessaryData = nft.interface.encodeFunctionData( 'createAndInitializePoolIfNecessary', - [await token0.getAddress(), await token1.getAddress(), ZERO_ADDRESS, encodePriceSqrt(1, 1)] + [await token0.getAddress(), await token1.getAddress(), ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'] ); await nft.multicall([createAndInitializePoolIfNecessaryData], { value: expandTo18Decimals(1) }); }); it('gas [ @skip-on-coverage ]', async () => { - await snapshotGasCost(nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1))); + await snapshotGasCost(nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x')); }); }); @@ -160,7 +160,7 @@ describe('NonfungiblePositionManager', () => { }); it('fails if cannot transfer', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await tokens[0].approve(nft, 0); await expect( nft.mint({ @@ -180,7 +180,7 @@ describe('NonfungiblePositionManager', () => { }); it('fails if deadline passed', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await nft.setTime(2); await expect( nft.mint({ @@ -204,7 +204,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ token0: tokens[0].getAddress(), @@ -253,7 +254,8 @@ describe('NonfungiblePositionManager', () => { await token0.getAddress(), await token1.getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1), + encodePriceSqrt(1, 1), + '0x' ]); const mintData = nft.interface.encodeFunctionData('mint', [ @@ -293,7 +295,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await snapshotGasCost( @@ -315,7 +318,7 @@ describe('NonfungiblePositionManager', () => { it('gas first mint for pool using eth with zero refund [ @skip-on-coverage ]', async () => { const [token0, token1] = await sortedTokens(wnative, tokens[0]); - await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await snapshotGasCost( nft.multicall( @@ -344,7 +347,7 @@ describe('NonfungiblePositionManager', () => { it('gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ]', async () => { const [token0, token1] = await sortedTokens(wnative, tokens[0]); - await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await snapshotGasCost( nft.multicall( @@ -372,7 +375,7 @@ describe('NonfungiblePositionManager', () => { }); it('gas mint on same ticks [ @skip-on-coverage ]', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await nft.mint({ token0: await tokens[0].getAddress(), @@ -410,7 +413,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -452,7 +456,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -523,7 +528,7 @@ describe('NonfungiblePositionManager', () => { const tokenId = 1; - await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); const mintData = nft.interface.encodeFunctionData('mint', [ { @@ -577,7 +582,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -701,7 +707,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -849,7 +856,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -930,7 +938,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -986,7 +995,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -1051,7 +1061,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -1111,7 +1122,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -1200,7 +1212,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ @@ -1240,7 +1253,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); // nft 1 earns 25% of fees await nft.mint({ @@ -1297,7 +1311,6 @@ describe('NonfungiblePositionManager', () => { amount0Max: MaxUint128, amount1Max: MaxUint128, }); - console.log(nft1Amount0.toString(), nft1Amount1.toString(), nft2Amount0.toString(), nft2Amount1.toString()); expect(nft1Amount0).to.eq(416); expect(nft1Amount1).to.eq(0); expect(nft2Amount0).to.eq(1250); @@ -1343,7 +1356,8 @@ describe('NonfungiblePositionManager', () => { tokens[0].getAddress(), tokens[1].getAddress(), ZERO_ADDRESS, - encodePriceSqrt(1, 1) + encodePriceSqrt(1, 1), + '0x' ); await nft.mint({ diff --git a/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts b/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts index 53a7b3093..87e854715 100644 --- a/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts +++ b/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts @@ -115,7 +115,7 @@ describe('NonfungibleTokenPositionDescriptor', () => { describe('#tokenURI', () => { it('displays Native as token symbol for WNativeToken token', async () => { const [token0, token1] = await sortedTokens(wnative, tokens[1]); - await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await wnative.approve(nft, 100); await tokens[1].approve(nft, 100); await nft.mint({ @@ -140,7 +140,7 @@ describe('NonfungibleTokenPositionDescriptor', () => { it('displays returned token symbols when neither token is WNativeToken ', async () => { const [token0, token1] = await sortedTokens(tokens[2], tokens[1]); - await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await tokens[1].approve(nft, 100); await tokens[2].approve(nft, 100); await nft.mint({ diff --git a/src/periphery/test/PositionValue.spec.ts b/src/periphery/test/PositionValue.spec.ts index b7a01f6fe..415a3aabb 100644 --- a/src/periphery/test/PositionValue.spec.ts +++ b/src/periphery/test/PositionValue.spec.ts @@ -40,7 +40,7 @@ describe('PositionValue', async () => { await token.transfer(wallets[0].address, expandTo18Decimals(1_000_000)); } - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); return { positionValue, diff --git a/src/periphery/test/QuoterV2.spec.ts b/src/periphery/test/QuoterV2.spec.ts index c478d6877..4c963e94b 100644 --- a/src/periphery/test/QuoterV2.spec.ts +++ b/src/periphery/test/QuoterV2.spec.ts @@ -85,21 +85,21 @@ describe('QuoterV2', function () { describe('#quoteExactInput', () => { it('0 -> 2 cross 2 tick', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 10000); ////await snapshotGasCost(gasEstimate) expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('78459826284680823468887704103'); expect(initializedTicksCrossedList[0]).to.eq(2); - expect(amountIn).to.eq(10000); - expect(amountOut).to.eq(9897); + expect(amountInList[0]).to.eq(10000); + expect(amountOutList[0]).to.eq(9897); }); it('0 -> 2 cross 2 tick where after is initialized', async () => { // The swap amount is set such that the active tick after the swap is -120. // -120 is an initialized tick for this pool. We check that we don't count it. - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 6200); ////await snapshotGasCost(gasEstimate) @@ -107,52 +107,52 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList[0]).to.eq('78755992497053066283316544500'); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(1); - expect(amountOut).to.eq(6158); - expect(amountIn).to.eq(6200); + expect(amountOutList[0]).to.eq(6158); + expect(amountInList[0]).to.eq(6200); }); it('0 -> 2 cross 1 tick', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 4000); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(1); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('78925679077027744088480448931'); - expect(amountOut).to.eq(3981); - expect(amountIn).to.eq(4000); + expect(amountOutList[0]).to.eq(3981); + expect(amountInList[0]).to.eq(4000); }); it('0 -> 2 cross 0 tick, starting tick not initialized', async () => { // Tick before 0, tick after -1. - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 10); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(0); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79227483487511329217250071027'); - expect(amountOut).to.eq(8); - expect(amountIn).to.eq(10); + expect(amountOutList[0]).to.eq(8); + expect(amountInList[0]).to.eq(10); }); it('0 -> 2 cross 0 tick, starting tick initialized', async () => { // Tick before 0, tick after -1. Tick 0 initialized. await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 10); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(1); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79227817515327498931091950511'); - expect(amountOut).to.eq(8); - expect(amountIn).to.eq(10); + expect(amountOutList[0]).to.eq(8); + expect(amountInList[0]).to.eq(10); }); it('2 -> 0 cross 2', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 10000); ////await snapshotGasCost(gasEstimate) @@ -160,32 +160,31 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('80004022856373268738318816658'); expect(initializedTicksCrossedList.length).to.eq(1); - expect(amountOut).to.eq(9897); - expect(amountIn).to.eq(10000); + expect(amountOutList[0]).to.eq(9897); + expect(amountInList[0]).to.eq(10000); }); it('2 -> 0 cross 2 where tick after is initialized', async () => { // The swap amount is set such that the active tick after the swap is 120. // 120 is an initialized tick for this pool. We check we don't count it. - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 6250); ////await snapshotGasCost(gasEstimate) - console.log(sqrtPriceX96AfterList[0].toString()); expect(initializedTicksCrossedList[0]).to.eq(2); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79706996475107291736680620388'); expect(initializedTicksCrossedList.length).to.eq(1); - expect(amountOut).to.eq(6206); - expect(amountIn).to.eq(6250); + expect(amountOutList[0]).to.eq(6206); + expect(amountInList[0]).to.eq(6250); }); it('2 -> 0 cross 0 tick, starting tick initialized', async () => { // Tick 0 initialized. Tick after = 1 await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 200); ////await snapshotGasCost(gasEstimate) @@ -193,13 +192,13 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79235729830182478001034429156'); expect(initializedTicksCrossedList.length).to.eq(1); - expect(amountOut).to.eq(198); - expect(amountIn).to.eq(200); + expect(amountOutList[0]).to.eq(198); + expect(amountInList[0]).to.eq(200); }); it('2 -> 0 cross 0 tick, starting tick not initialized', async () => { // Tick 0 initialized. Tick after = 1 - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 103); ////await snapshotGasCost(gasEstimate) @@ -207,12 +206,12 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79235858216754624215638319723'); expect(initializedTicksCrossedList.length).to.eq(1); - expect(amountOut).to.eq(101); - expect(amountIn).to.eq(103); + expect(amountOutList[0]).to.eq(101); + expect(amountInList[0]).to.eq(103); }); it('2 -> 1', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall(encodePath([path[4], path[3], path[2]]), 10000); @@ -220,12 +219,12 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('80020047998594409647791422119'); expect(initializedTicksCrossedList[0]).to.eq(0); - expect(amountOut).to.eq(9896); - expect(amountIn).to.eq(10000); + expect(amountOutList[0]).to.eq(9896); + expect(amountInList[0]).to.eq(10000); }); it('0 -> 2 -> 1', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall( encodePath([path[0], ZERO_ADDRESS, path[4], path[3], path[2]]), 10000 @@ -237,8 +236,8 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList[1]).to.eq('80011887497855440421019287092'); expect(initializedTicksCrossedList[0]).to.eq(2); expect(initializedTicksCrossedList[1]).to.eq(0); - expect(amountOut).to.eq(9795); - expect(amountIn).to.eq(10000); + expect(amountOutList[1]).to.eq(9795); + expect(amountInList[0]).to.eq(10000); }); }); @@ -339,13 +338,13 @@ describe('QuoterV2', function () { describe('#quoteExactOutput', () => { it('0 -> 2 cross 2 tick', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 15000); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(2); - expect(amountIn).to.eq(15234); - expect(amountOut).to.be.eq(15000); + expect(amountInList[0]).to.eq(15234); + expect(amountOutList[0]).to.be.eq(15000); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('78055527257643669242286029831'); @@ -354,25 +353,25 @@ describe('QuoterV2', function () { it('0 -> 2 cross 2 where tick after is initialized', async () => { // The swap amount is set such that the active tick after the swap is -120. // -120 is an initialized tick for this pool. We check that we count it. - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 6158); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('78756056567076985409608047254'); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(1); - expect(amountIn).to.eq(6200); - expect(amountOut).to.be.eq(6158); + expect(amountInList[0]).to.eq(6200); + expect(amountOutList[0]).to.be.eq(6158); }); it('0 -> 2 cross 1 tick', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 4000); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(1); - expect(amountIn).to.eq(4019); - expect(amountOut).to.be.eq(4000); + expect(amountInList[0]).to.eq(4019); + expect(amountOutList[0]).to.be.eq(4000); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('78924219757724709840818372098'); @@ -381,39 +380,39 @@ describe('QuoterV2', function () { it('0 -> 2 cross 0 tick starting tick initialized', async () => { // Tick before 0, tick after 1. Tick 0 initialized. await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 100); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(1); - expect(amountIn).to.eq(102); - expect(amountOut).to.be.eq(100); + expect(amountInList[0]).to.eq(102); + expect(amountOutList[0]).to.be.eq(100); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79224329176051641448521403903'); }); it('0 -> 2 cross 0 tick starting tick not initialized', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 10); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(0); - expect(amountIn).to.eq(12); - expect(amountOut).to.be.eq(10); + expect(amountInList[0]).to.eq(12); + expect(amountOutList[0]).to.be.eq(10); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79227408033628034983534698435'); }); it('2 -> 0 cross 2 ticks', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 15000); expect(initializedTicksCrossedList.length).to.eq(1); expect(initializedTicksCrossedList[0]).to.eq(2); - expect(amountIn).to.eq(15234); - expect(amountOut).to.be.eq(15000); + expect(amountInList[0]).to.eq(15234); + expect(amountOutList[0]).to.be.eq(15000); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('80418414376567919517220409857'); }); @@ -421,42 +420,42 @@ describe('QuoterV2', function () { it('2 -> 0 cross 2 where tick after is initialized', async () => { // The swap amount is set such that the active tick after the swap is 120. // 120 is an initialized tick for this pool. We check that we don't count it. - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 6223); expect(initializedTicksCrossedList[0]).to.eq(2); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79708304437530892332449657932'); expect(initializedTicksCrossedList.length).to.eq(1); - expect(amountIn).to.eq(6267); - expect(amountOut).to.be.eq(6223); + expect(amountInList[0]).to.eq(6267); + expect(amountOutList[0]).to.be.eq(6223); }); it('2 -> 0 cross 1 tick', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 6000); expect(initializedTicksCrossedList[0]).to.eq(1); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('79690640184021170956740081887'); expect(initializedTicksCrossedList.length).to.eq(1); - expect(amountIn).to.eq(6040); - expect(amountOut).to.be.eq(6000); + expect(amountInList[0]).to.eq(6040); + expect(amountOutList[0]).to.be.eq(6000); }); it('2 -> 1', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall(encodePath([path[2], path[3], path[4]]), 9897); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('80020121658316697953186638498'); expect(initializedTicksCrossedList[0]).to.eq(0); - expect(amountIn).to.eq(10002); - expect(amountOut).to.be.eq(9897); + expect(amountInList[0]).to.eq(10002); + expect(amountOutList[0]).to.be.eq(9897); }); it('0 -> 2 -> 1', async () => { - const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = + const { amountOutList, amountInList, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall( encodePath([path[0], ZERO_ADDRESS, path[4], path[3], path[2]].reverse()), 9795 @@ -467,8 +466,10 @@ describe('QuoterV2', function () { expect(sqrtPriceX96AfterList[1]).to.eq('78459828570953960157025884610'); expect(initializedTicksCrossedList[0]).to.eq(0); expect(initializedTicksCrossedList[1]).to.eq(2); - expect(amountIn).to.eq(10000); - expect(amountOut).to.be.eq(9795); + expect(amountInList[0]).to.eq(9897); + expect(amountInList[1]).to.eq(10000); + expect(amountOutList[0]).to.be.eq(9795); + expect(amountOutList[1]).to.eq(9897); }); describe('gas [ @skip-on-coverage ]', () => { diff --git a/src/periphery/test/SwapRouter.spec.ts b/src/periphery/test/SwapRouter.spec.ts index 2e358112e..1285ad447 100644 --- a/src/periphery/test/SwapRouter.spec.ts +++ b/src/periphery/test/SwapRouter.spec.ts @@ -45,7 +45,7 @@ describe('SwapRouter', function () { if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, deployer, encodePriceSqrt(1, 1)); + await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, deployer, encodePriceSqrt(1, 1), '0x'); const liquidityParams = { token0: tokenAddressA, diff --git a/src/periphery/test/TickLens.spec.ts b/src/periphery/test/TickLens.spec.ts index d9a62ca9a..06ba8ec13 100644 --- a/src/periphery/test/TickLens.spec.ts +++ b/src/periphery/test/TickLens.spec.ts @@ -78,7 +78,7 @@ describe('TickLens', () => { if (BigInt(tokenAddressA) > BigInt(tokenAddressB)) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - const tx = await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + const tx = await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await tx.wait(); const liquidityParams = { diff --git a/src/periphery/test/V3Migrator.spec.ts b/src/periphery/test/V3Migrator.spec.ts index ea60a44bf..be596283e 100644 --- a/src/periphery/test/V3Migrator.spec.ts +++ b/src/periphery/test/V3Migrator.spec.ts @@ -143,7 +143,7 @@ describe('V3Migrator', () => { it('works once v3 pool is initialized', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await pair.approve(migrator, expectedLiquidity); await migrator.migrate({ @@ -172,7 +172,7 @@ describe('V3Migrator', () => { it('works for partial', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); const tokenBalanceBefore = await token.balanceOf(wallet.address); const wnativeBalanceBefore = await wnative.balanceOf(wallet.address); @@ -210,7 +210,7 @@ describe('V3Migrator', () => { it('double the price', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(2, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(2, 1), '0x'); const tokenBalanceBefore = await token.balanceOf(wallet.address); const wnativeBalanceBefore = await wnative.balanceOf(wallet.address); @@ -254,7 +254,7 @@ describe('V3Migrator', () => { it('half the price', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 2)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 2), '0x'); const tokenBalanceBefore = await token.balanceOf(wallet.address); const wnativeBalanceBefore = await wnative.balanceOf(wallet.address); @@ -298,7 +298,7 @@ describe('V3Migrator', () => { it('double the price - as Native', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(2, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(2, 1), '0x'); const tokenBalanceBefore = await token.balanceOf(wallet.address); @@ -342,7 +342,7 @@ describe('V3Migrator', () => { it('half the price - as Native', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 2)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 2), '0x'); const tokenBalanceBefore = await token.balanceOf(wallet.address); @@ -386,7 +386,7 @@ describe('V3Migrator', () => { it('gas [ @skip-on-coverage ]', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); await pair.approve(migrator, expectedLiquidity); await snapshotGasCost( diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap index f245f1c65..158095bd3 100644 --- a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NFTDescriptor #constructTokenURI gas [ @skip-on-coverage ] 1`] = `1625388`; +exports[`NFTDescriptor #constructTokenURI gas [ @skip-on-coverage ] 1`] = `1625406`; exports[`NFTDescriptor #constructTokenURI snapshot matches 1`] = `"data:application/json;base64,eyJuYW1lIjoiQWxnZWJyYSAtIFVOSS9XTmF0aXZlVG9rZW4gLSAxLjAwMDA8PjEuMTA1MiIsICJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYSBsaXF1aWRpdHkgcG9zaXRpb24gaW4gYSBBbGdlYnJhIFVOSS1XTmF0aXZlVG9rZW4gcG9vbC4gVGhlIG93bmVyIG9mIHRoaXMgTkZUIGNhbiBtb2RpZnkgb3IgcmVkZWVtIHRoZSBwb3NpdGlvbi5cblxuUG9vbCBBZGRyZXNzOiAweGJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJcblVOSSBBZGRyZXNzOiAweGFiY2RlYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGZcbldOYXRpdmVUb2tlbiBBZGRyZXNzOiAweDEyMzQ1Njc4OTAxMjM0NTY3ODkxMjM0NTY3ODkwMTIzNDU2Nzg5MDFcblRva2VuIElEOiAxXG5cbuKaoO+4jyBESVNDTEFJTUVSOiBEdWUgZGlsaWdlbmNlIGlzIGltcGVyYXRpdmUgd2hlbiBhc3Nlc3NpbmcgdGhpcyBORlQuIE1ha2Ugc3VyZSB0b2tlbiBhZGRyZXNzZXMgbWF0Y2ggdGhlIGV4cGVjdGVkIHRva2VucywgYXMgdG9rZW4gc3ltYm9scyBtYXkgYmUgaW1pdGF0ZWQuIiwgImltYWdlIjogImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjNhV1IwYUQwaU1qa3dJaUJvWldsbmFIUTlJalV3TUNJZ2RtbGxkMEp2ZUQwaU1DQXdJREk1TUNBMU1EQWlJSGh0Ykc1elBTSm9kSFJ3T2k4dmQzZDNMbmN6TG05eVp5OHlNREF3TDNOMlp5SWdlRzFzYm5NNmVHeHBibXM5SjJoMGRIQTZMeTkzZDNjdWR6TXViM0puTHpFNU9Ua3ZlR3hwYm1zblBqeGtaV1p6UGp4bWFXeDBaWElnYVdROUltWXhJajQ4Wm1WSmJXRm5aU0J5WlhOMWJIUTlJbkF3SWlCNGJHbHVhenBvY21WbVBTSmtZWFJoT21sdFlXZGxMM04yWnl0NGJXdzdZbUZ6WlRZMExGQklUakphZVVJellWZFNNR0ZFTUc1TmFtdDNTbmxDYjFwWGJHNWhTRkU1U25wVmQwMURZMmRrYld4c1pEQktkbVZFTUc1TlEwRjNTVVJKTlUxRFFURk5SRUZ1U1Vob2RHSkhOWHBRVTJSdlpFaFNkMDlwT0haa00yUXpURzVqZWt4dE9YbGFlVGg1VFVSQmQwd3pUakphZVdNclVFaEtiRmt6VVdka01teHJaRWRuT1VwNlNUVk5TRUkwU25sQ2IxcFhiRzVoU0ZFNVNucFZkMDFJUWpSS2VVSnRZVmQ0YzFCVFkycFpWMHBxV2tkV2FFcDVPQ3RRUXpsNlpHMWpLeUl2UGp4bVpVbHRZV2RsSUhKbGMzVnNkRDBpY0RFaUlIaHNhVzVyT21oeVpXWTlJbVJoZEdFNmFXMWhaMlV2YzNabkszaHRiRHRpWVhObE5qUXNVRWhPTWxwNVFqTmhWMUl3WVVRd2JrMXFhM2RLZVVKdldsZHNibUZJVVRsS2VsVjNUVU5qWjJSdGJHeGtNRXAyWlVRd2JrMURRWGRKUkVrMVRVTkJNVTFFUVc1SlNHaDBZa2MxZWxCVFpHOWtTRkozVDJrNGRtUXpaRE5NYm1ONlRHMDVlVnA1T0hsTlJFRjNURE5PTWxwNVl5dFFSMDV3WTIxT2MxcFRRbXBsUkRCdVRXcFpORXA1UW1wbFZEQnVUV3BWTVVwNVFubFFVMk40VFdwQ2QyVkRZMmRhYld4ellrUXdia2w2UlhsTmVsRXhUbWxqZGxCcWQzWmpNMXB1VUdjOVBTSXZQanhtWlVsdFlXZGxJSEpsYzNWc2REMGljRElpSUhoc2FXNXJPbWh5WldZOUltUmhkR0U2YVcxaFoyVXZjM1puSzNodGJEdGlZWE5sTmpRc1VFaE9NbHA1UWpOaFYxSXdZVVF3YmsxcWEzZEtlVUp2V2xkc2JtRklVVGxLZWxWM1RVTmpaMlJ0Ykd4a01FcDJaVVF3YmsxRFFYZEpSRWsxVFVOQk1VMUVRVzVKU0doMFlrYzFlbEJUWkc5a1NGSjNUMms0ZG1RelpETk1ibU42VEcwNWVWcDVPSGxOUkVGM1RETk9NbHA1WXl0UVIwNXdZMjFPYzFwVFFtcGxSREJ1VFdwQk1rcDVRbXBsVkRCdVRWUlZlVXA1UW5sUVUyTjRUV3BDZDJWRFkyZGFiV3h6WWtRd2Jra3lXbWhaYlU1cldtbGpkbEJxZDNaak0xcHVVR2M5UFNJZ0x6NDhabVZKYldGblpTQnlaWE4xYkhROUluQXpJaUI0YkdsdWF6cG9jbVZtUFNKa1lYUmhPbWx0WVdkbEwzTjJaeXQ0Yld3N1ltRnpaVFkwTEZCSVRqSmFlVUl6WVZkU01HRkVNRzVOYW10M1NubENiMXBYYkc1aFNGRTVTbnBWZDAxRFkyZGtiV3hzWkRCS2RtVkVNRzVOUTBGM1NVUkpOVTFEUVRGTlJFRnVTVWhvZEdKSE5YcFFVMlJ2WkVoU2QwOXBPSFprTTJRelRHNWpla3h0T1hsYWVUaDVUVVJCZDB3elRqSmFlV01yVUVkT2NHTnRUbk5hVTBKcVpVUXdiazFxVVhkS2VVSnFaVlF3YmsxNlFUSktlVUo1VUZOamVFMUVRbmRsUTJObldtMXNjMkpFTUc1SmVsa3pUMFJyZDAxVFkzWlFhbmQyWXpOYWJsQm5QVDBpSUM4K1BHWmxRbXhsYm1RZ2JXOWtaVDBpYjNabGNteGhlU0lnYVc0OUluQXdJaUJwYmpJOUluQXhJaUF2UGp4bVpVSnNaVzVrSUcxdlpHVTlJbVY0WTJ4MWMybHZiaUlnYVc0eVBTSndNaUlnTHo0OFptVkNiR1Z1WkNCdGIyUmxQU0p2ZG1WeWJHRjVJaUJwYmpJOUluQXpJaUJ5WlhOMWJIUTlJbUpzWlc1a1QzVjBJaUF2UGp4bVpVZGhkWE56YVdGdVFteDFjaUJwYmowaVlteGxibVJQZFhRaUlITjBaRVJsZG1saGRHbHZiajBpTkRJaUlDOCtQQzltYVd4MFpYSStJRHhqYkdsd1VHRjBhQ0JwWkQwaVkyOXlibVZ5Y3lJK1BISmxZM1FnZDJsa2RHZzlJakk1TUNJZ2FHVnBaMmgwUFNJMU1EQWlJSEo0UFNJME1pSWdjbms5SWpReUlpQXZQand2WTJ4cGNGQmhkR2crUEhCaGRHZ2dhV1E5SW5SbGVIUXRjR0YwYUMxaElpQmtQU0pOTkRBZ01USWdTREkxTUNCQk1qZ2dNamdnTUNBd0lERWdNamM0SURRd0lGWTBOakFnUVRJNElESTRJREFnTUNBeElESTFNQ0EwT0RnZ1NEUXdJRUV5T0NBeU9DQXdJREFnTVNBeE1pQTBOakFnVmpRd0lFRXlPQ0F5T0NBd0lEQWdNU0EwTUNBeE1pQjZJaUF2UGp4d1lYUm9JR2xrUFNKdGFXNXBiV0Z3SWlCa1BTSk5Nak0wSURRME5FTXlNelFnTkRVM0xqazBPU0F5TkRJdU1qRWdORFl6SURJMU15QTBOak1pSUM4K1BHWnBiSFJsY2lCcFpEMGlkRzl3TFhKbFoybHZiaTFpYkhWeUlqNDhabVZIWVhWemMybGhia0pzZFhJZ2FXNDlJbE52ZFhKalpVZHlZWEJvYVdNaUlITjBaRVJsZG1saGRHbHZiajBpTWpRaUlDOCtQQzltYVd4MFpYSStQR3hwYm1WaGNrZHlZV1JwWlc1MElHbGtQU0puY21Ga0xYVndJaUI0TVQwaU1TSWdlREk5SWpBaUlIa3hQU0l4SWlCNU1qMGlNQ0krUEhOMGIzQWdiMlptYzJWMFBTSXdMakFpSUhOMGIzQXRZMjlzYjNJOUluZG9hWFJsSWlCemRHOXdMVzl3WVdOcGRIazlJakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l1T1NJZ2MzUnZjQzFqYjJ4dmNqMGlkMmhwZEdVaUlITjBiM0F0YjNCaFkybDBlVDBpTUNJZ0x6NDhMMnhwYm1WaGNrZHlZV1JwWlc1MFBqeHNhVzVsWVhKSGNtRmthV1Z1ZENCcFpEMGlaM0poWkMxa2IzZHVJaUI0TVQwaU1DSWdlREk5SWpFaUlIa3hQU0l3SWlCNU1qMGlNU0krUEhOMGIzQWdiMlptYzJWMFBTSXdMakFpSUhOMGIzQXRZMjlzYjNJOUluZG9hWFJsSWlCemRHOXdMVzl3WVdOcGRIazlJakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l3TGpraUlITjBiM0F0WTI5c2IzSTlJbmRvYVhSbElpQnpkRzl3TFc5d1lXTnBkSGs5SWpBaUlDOCtQQzlzYVc1bFlYSkhjbUZrYVdWdWRENDhiV0Z6YXlCcFpEMGlabUZrWlMxMWNDSWdiV0Z6YTBOdmJuUmxiblJWYm1sMGN6MGliMkpxWldOMFFtOTFibVJwYm1kQ2IzZ2lQanh5WldOMElIZHBaSFJvUFNJeElpQm9aV2xuYUhROUlqRWlJR1pwYkd3OUluVnliQ2dqWjNKaFpDMTFjQ2tpSUM4K1BDOXRZWE5yUGp4dFlYTnJJR2xrUFNKbVlXUmxMV1J2ZDI0aUlHMWhjMnREYjI1MFpXNTBWVzVwZEhNOUltOWlhbVZqZEVKdmRXNWthVzVuUW05NElqNDhjbVZqZENCM2FXUjBhRDBpTVNJZ2FHVnBaMmgwUFNJeElpQm1hV3hzUFNKMWNtd29JMmR5WVdRdFpHOTNiaWtpSUM4K1BDOXRZWE5yUGp4dFlYTnJJR2xrUFNKdWIyNWxJaUJ0WVhOclEyOXVkR1Z1ZEZWdWFYUnpQU0p2WW1wbFkzUkNiM1Z1WkdsdVowSnZlQ0krUEhKbFkzUWdkMmxrZEdnOUlqRWlJR2hsYVdkb2REMGlNU0lnWm1sc2JEMGlkMmhwZEdVaUlDOCtQQzl0WVhOclBqeHNhVzVsWVhKSGNtRmthV1Z1ZENCcFpEMGlaM0poWkMxemVXMWliMndpUGp4emRHOXdJRzltWm5ObGREMGlNQzQzSWlCemRHOXdMV052Ykc5eVBTSjNhR2wwWlNJZ2MzUnZjQzF2Y0dGamFYUjVQU0l4SWlBdlBqeHpkRzl3SUc5bVpuTmxkRDBpTGprMUlpQnpkRzl3TFdOdmJHOXlQU0ozYUdsMFpTSWdjM1J2Y0MxdmNHRmphWFI1UFNJd0lpQXZQand2YkdsdVpXRnlSM0poWkdsbGJuUStQRzFoYzJzZ2FXUTlJbVpoWkdVdGMzbHRZbTlzSWlCdFlYTnJRMjl1ZEdWdWRGVnVhWFJ6UFNKMWMyVnlVM0JoWTJWUGJsVnpaU0krUEhKbFkzUWdkMmxrZEdnOUlqSTVNSEI0SWlCb1pXbG5hSFE5SWpJd01IQjRJaUJtYVd4c1BTSjFjbXdvSTJkeVlXUXRjM2x0WW05c0tTSWdMejQ4TDIxaGMycytQQzlrWldaelBqeG5JR05zYVhBdGNHRjBhRDBpZFhKc0tDTmpiM0p1WlhKektTSStQSEpsWTNRZ1ptbHNiRDBpWVdKalpHVmhJaUI0UFNJd2NIZ2lJSGs5SWpCd2VDSWdkMmxrZEdnOUlqSTVNSEI0SWlCb1pXbG5hSFE5SWpVd01IQjRJaUF2UGp4eVpXTjBJSE4wZVd4bFBTSm1hV3gwWlhJNklIVnliQ2dqWmpFcElpQjRQU0l3Y0hnaUlIazlJakJ3ZUNJZ2QybGtkR2c5SWpJNU1IQjRJaUJvWldsbmFIUTlJalV3TUhCNElpQXZQaUE4WnlCemRIbHNaVDBpWm1sc2RHVnlPblZ5YkNnamRHOXdMWEpsWjJsdmJpMWliSFZ5S1RzZ2RISmhibk5tYjNKdE9uTmpZV3hsS0RFdU5TazdJSFJ5WVc1elptOXliUzF2Y21sbmFXNDZZMlZ1ZEdWeUlIUnZjRHNpUGp4eVpXTjBJR1pwYkd3OUltNXZibVVpSUhnOUlqQndlQ0lnZVQwaU1IQjRJaUIzYVdSMGFEMGlNamt3Y0hnaUlHaGxhV2RvZEQwaU5UQXdjSGdpSUM4K1BHVnNiR2x3YzJVZ1kzZzlJalV3SlNJZ1kzazlJakJ3ZUNJZ2NuZzlJakU0TUhCNElpQnllVDBpTVRJd2NIZ2lJR1pwYkd3OUlpTXdNREFpSUc5d1lXTnBkSGs5SWpBdU9EVWlJQzgrUEM5blBqeHlaV04wSUhnOUlqQWlJSGs5SWpBaUlIZHBaSFJvUFNJeU9UQWlJR2hsYVdkb2REMGlOVEF3SWlCeWVEMGlORElpSUhKNVBTSTBNaUlnWm1sc2JEMGljbWRpWVNnd0xEQXNNQ3d3S1NJZ2MzUnliMnRsUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERBdU1pa2lJQzgrUEM5blBqeDBaWGgwSUhSbGVIUXRjbVZ1WkdWeWFXNW5QU0p2Y0hScGJXbDZaVk53WldWa0lqNDhkR1Y0ZEZCaGRHZ2djM1JoY25SUFptWnpaWFE5SWkweE1EQWxJaUJtYVd4c1BTSjNhR2wwWlNJZ1ptOXVkQzFtWVcxcGJIazlJaWREYjNWeWFXVnlJRTVsZHljc0lHMXZibTl6Y0dGalpTSWdabTl1ZEMxemFYcGxQU0l4TUhCNElpQjRiR2x1YXpwb2NtVm1QU0lqZEdWNGRDMXdZWFJvTFdFaVBqQjRNVEl6TkRVMk56ZzVNREV5TXpRMU5qYzRPVEV5TXpRMU5qYzRPVEF4TWpNME5UWTNPRGt3TVNEaWdLSWdWMDVoZEdsMlpWUnZhMlZ1SUR4aGJtbHRZWFJsSUdGa1pHbDBhWFpsUFNKemRXMGlJR0YwZEhKcFluVjBaVTVoYldVOUluTjBZWEowVDJabWMyVjBJaUJtY205dFBTSXdKU0lnZEc4OUlqRXdNQ1VpSUdKbFoybHVQU0l3Y3lJZ1pIVnlQU0l6TUhNaUlISmxjR1ZoZEVOdmRXNTBQU0pwYm1SbFptbHVhWFJsSWlBdlBqd3ZkR1Y0ZEZCaGRHZytJRHgwWlhoMFVHRjBhQ0J6ZEdGeWRFOW1abk5sZEQwaU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IZ3hNak0wTlRZM09Ea3dNVEl6TkRVMk56ZzVNVEl6TkRVMk56ZzVNREV5TXpRMU5qYzRPVEF4SU9LQW9pQlhUbUYwYVhabFZHOXJaVzRnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K0lEd3ZkR1Y0ZEZCaGRHZytQSFJsZUhSUVlYUm9JSE4wWVhKMFQyWm1jMlYwUFNJMU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IaGhZbU5rWldGaVkyUmxabUZpWTJSbFptRmlZMlJsWm1GaVkyUmxabUZpWTJSbFptRmlZMlJtSU9LQW9pQlZUa2tnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K1BDOTBaWGgwVUdGMGFENDhkR1Y0ZEZCaGRHZ2djM1JoY25SUFptWnpaWFE5SWkwMU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IaGhZbU5rWldGaVkyUmxabUZpWTJSbFptRmlZMlJsWm1GaVkyUmxabUZpWTJSbFptRmlZMlJtSU9LQW9pQlZUa2tnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K1BDOTBaWGgwVUdGMGFENDhMM1JsZUhRK1BHY2diV0Z6YXowaWRYSnNLQ05tWVdSbExYTjViV0p2YkNraVBqeHlaV04wSUdacGJHdzlJbTV2Ym1VaUlIZzlJakJ3ZUNJZ2VUMGlNSEI0SWlCM2FXUjBhRDBpTWprd2NIZ2lJR2hsYVdkb2REMGlNakF3Y0hnaUlDOCtJRHgwWlhoMElIazlJamN3Y0hnaUlIZzlJak15Y0hnaUlHWnBiR3c5SW5kb2FYUmxJaUJtYjI1MExXWmhiV2xzZVQwaUowTnZkWEpwWlhJZ1RtVjNKeXdnYlc5dWIzTndZV05sSWlCbWIyNTBMWGRsYVdkb2REMGlNakF3SWlCbWIyNTBMWE5wZW1VOUlqTTJjSGdpUGxWT1NTOVhUbUYwYVhabFZHOXJaVzQ4TDNSbGVIUStQQzluUGp4eVpXTjBJSGc5SWpFMklpQjVQU0l4TmlJZ2QybGtkR2c5SWpJMU9DSWdhR1ZwWjJoMFBTSTBOamdpSUhKNFBTSXlOaUlnY25rOUlqSTJJaUJtYVd4c1BTSnlaMkpoS0RBc01Dd3dMREFwSWlCemRISnZhMlU5SW5KblltRW9NalUxTERJMU5Td3lOVFVzTUM0eUtTSWdMejQ4WnlCdFlYTnJQU0oxY213b0kyWmhaR1V0Wkc5M2Jpa2lJSE4wZVd4bFBTSjBjbUZ1YzJadmNtMDZkSEpoYm5Oc1lYUmxLRGN5Y0hnc01UZzVjSGdwSWo0OGNtVmpkQ0I0UFNJdE1UWndlQ0lnZVQwaUxURTJjSGdpSUhkcFpIUm9QU0l4T0RCd2VDSWdhR1ZwWjJoMFBTSXhPREJ3ZUNJZ1ptbHNiRDBpYm05dVpTSWdMejQ4Y0dGMGFDQmtQU0pOTVNBeFF6TXpJRFUzSURnNUlERXhNeUF4TkRVZ01UUTFJaUJ6ZEhKdmEyVTlJbkpuWW1Fb01Dd3dMREFzTUM0ektTSWdjM1J5YjJ0bExYZHBaSFJvUFNJek1uQjRJaUJtYVd4c1BTSnViMjVsSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJQzgrUEM5blBqeG5JRzFoYzJzOUluVnliQ2dqWm1Ga1pTMWtiM2R1S1NJZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVb056SndlQ3d4T0Rsd2VDa2lQanh5WldOMElIZzlJaTB4Tm5CNElpQjVQU0l0TVRad2VDSWdkMmxrZEdnOUlqRTRNSEI0SWlCb1pXbG5hSFE5SWpFNE1IQjRJaUJtYVd4c1BTSnViMjVsSWlBdlBqeHdZWFJvSUdROUlrMHhJREZETXpNZ05UY2dPRGtnTVRFeklERTBOU0F4TkRVaUlITjBjbTlyWlQwaWNtZGlZU2d5TlRVc01qVTFMREkxTlN3eEtTSWdabWxzYkQwaWJtOXVaU0lnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lpQXZQand2Wno0OFkybHlZMnhsSUdONFBTSTNNM0I0SWlCamVUMGlNVGt3Y0hnaUlISTlJalJ3ZUNJZ1ptbHNiRDBpZDJocGRHVWlJQzgrUEdOcGNtTnNaU0JqZUQwaU56TndlQ0lnWTNrOUlqRTVNSEI0SWlCeVBTSXlOSEI0SWlCbWFXeHNQU0p1YjI1bElpQnpkSEp2YTJVOUluZG9hWFJsSWlBdlBpQThaeUJ6ZEhsc1pUMGlkSEpoYm5ObWIzSnRPblJ5WVc1emJHRjBaU2d5T1hCNExDQXpPRFJ3ZUNraVBqeHlaV04wSUhkcFpIUm9QU0kyTTNCNElpQm9aV2xuYUhROUlqSTJjSGdpSUhKNFBTSTRjSGdpSUhKNVBTSTRjSGdpSUdacGJHdzlJbkpuWW1Fb01Dd3dMREFzTUM0MktTSWdMejQ4ZEdWNGRDQjRQU0l4TW5CNElpQjVQU0l4TjNCNElpQm1iMjUwTFdaaGJXbHNlVDBpSjBOdmRYSnBaWElnVG1WM0p5d2diVzl1YjNOd1lXTmxJaUJtYjI1MExYTnBlbVU5SWpFeWNIZ2lJR1pwYkd3OUluZG9hWFJsSWo0OGRITndZVzRnWm1sc2JEMGljbWRpWVNneU5UVXNNalUxTERJMU5Td3dMallwSWo1SlJEb2dQQzkwYzNCaGJqNHhQQzkwWlhoMFBqd3ZaejRnUEdjZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVb01qbHdlQ3dnTkRFMGNIZ3BJajQ4Y21WamRDQjNhV1IwYUQwaU1UQTFjSGdpSUdobGFXZG9kRDBpTWpad2VDSWdjbmc5SWpod2VDSWdjbms5SWpod2VDSWdabWxzYkQwaWNtZGlZU2d3TERBc01Dd3dMallwSWlBdlBqeDBaWGgwSUhnOUlqRXljSGdpSUhrOUlqRTNjSGdpSUdadmJuUXRabUZ0YVd4NVBTSW5RMjkxY21sbGNpQk9aWGNuTENCdGIyNXZjM0JoWTJVaUlHWnZiblF0YzJsNlpUMGlNVEp3ZUNJZ1ptbHNiRDBpZDJocGRHVWlQangwYzNCaGJpQm1hV3hzUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERBdU5pa2lQazFwYmlCVWFXTnJPaUE4TDNSemNHRnVQakE4TDNSbGVIUStQQzluUGlBOFp5QnpkSGxzWlQwaWRISmhibk5tYjNKdE9uUnlZVzV6YkdGMFpTZ3lPWEI0TENBME5EUndlQ2tpUGp4eVpXTjBJSGRwWkhSb1BTSXhNalp3ZUNJZ2FHVnBaMmgwUFNJeU5uQjRJaUJ5ZUQwaU9IQjRJaUJ5ZVQwaU9IQjRJaUJtYVd4c1BTSnlaMkpoS0RBc01Dd3dMREF1TmlraUlDOCtQSFJsZUhRZ2VEMGlNVEp3ZUNJZ2VUMGlNVGR3ZUNJZ1ptOXVkQzFtWVcxcGJIazlJaWREYjNWeWFXVnlJRTVsZHljc0lHMXZibTl6Y0dGalpTSWdabTl1ZEMxemFYcGxQU0l4TW5CNElpQm1hV3hzUFNKM2FHbDBaU0krUEhSemNHRnVJR1pwYkd3OUluSm5ZbUVvTWpVMUxESTFOU3d5TlRVc01DNDJLU0krVFdGNElGUnBZMnM2SUR3dmRITndZVzQrTVRBd01Ed3ZkR1Y0ZEQ0OEwyYytQR2NnYzNSNWJHVTlJblJ5WVc1elptOXliVHAwY21GdWMyeGhkR1VvTWpJMmNIZ3NJRFF6TTNCNEtTSStQSEpsWTNRZ2QybGtkR2c5SWpNMmNIZ2lJR2hsYVdkb2REMGlNelp3ZUNJZ2NuZzlJamh3ZUNJZ2NuazlJamh3ZUNJZ1ptbHNiRDBpYm05dVpTSWdjM1J5YjJ0bFBTSnlaMkpoS0RJMU5Td3lOVFVzTWpVMUxEQXVNaWtpSUM4K1BIQmhkR2dnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lpQmtQU0pOT0NBNVF6Z3VNREF3TURRZ01qSXVPVFE1TkNBeE5pNHlNRGs1SURJNElESTNJREk0SWlCbWFXeHNQU0p1YjI1bElpQnpkSEp2YTJVOUluZG9hWFJsSWlBdlBqeGphWEpqYkdVZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVelpDZ3hNM0I0TENBeU0zQjRMQ0F3Y0hncElpQmplRDBpTUhCNElpQmplVDBpTUhCNElpQnlQU0kwY0hnaUlHWnBiR3c5SW5kb2FYUmxJaTgrUEM5blBqeG5JSE4wZVd4bFBTSjBjbUZ1YzJadmNtMDZkSEpoYm5Oc1lYUmxLREl5Tm5CNExDQXpPVEp3ZUNraVBqeHlaV04wSUhkcFpIUm9QU0l6Tm5CNElpQm9aV2xuYUhROUlqTTJjSGdpSUhKNFBTSTRjSGdpSUhKNVBTSTRjSGdpSUdacGJHdzlJbTV2Ym1VaUlITjBjbTlyWlQwaWNtZGlZU2d5TlRVc01qVTFMREkxTlN3d0xqSXBJaUF2UGp4blBqeHdZWFJvSUhOMGVXeGxQU0owY21GdWMyWnZjbTA2ZEhKaGJuTnNZWFJsS0Rad2VDdzJjSGdwSWlCa1BTSk5NVElnTUV3eE1pNDJOVEl5SURrdU5UWTFPRGRNTVRnZ01TNDJNRGMzVERFekxqYzRNVGtnTVRBdU1qRTRNVXd5TWk0ek9USXpJRFpNTVRRdU5ETTBNU0F4TVM0ek5EYzRUREkwSURFeVRERTBMalF6TkRFZ01USXVOalV5TWt3eU1pNHpPVEl6SURFNFRERXpMamM0TVRrZ01UTXVOemd4T1V3eE9DQXlNaTR6T1RJelRERXlMalkxTWpJZ01UUXVORE0wTVV3eE1pQXlORXd4TVM0ek5EYzRJREUwTGpRek5ERk1OaUF5TWk0ek9USXpUREV3TGpJeE9ERWdNVE11TnpneE9Vd3hMall3TnpjZ01UaE1PUzQxTmpVNE55QXhNaTQyTlRJeVREQWdNVEpNT1M0MU5qVTROeUF4TVM0ek5EYzRUREV1TmpBM055QTJUREV3TGpJeE9ERWdNVEF1TWpFNE1VdzJJREV1TmpBM04wd3hNUzR6TkRjNElEa3VOVFkxT0RkTU1USWdNRm9pSUdacGJHdzlJbmRvYVhSbElpQXZQanhoYm1sdFlYUmxWSEpoYm5ObWIzSnRJR0YwZEhKcFluVjBaVTVoYldVOUluUnlZVzV6Wm05eWJTSWdkSGx3WlQwaWNtOTBZWFJsSWlCbWNtOXRQU0l3SURFNElERTRJaUIwYnowaU16WXdJREU0SURFNElpQmtkWEk5SWpFd2N5SWdjbVZ3WldGMFEyOTFiblE5SW1sdVpHVm1hVzVwZEdVaUx6NDhMMmMrUEM5blBqd3ZjM1puUGc9PSJ9"`; diff --git a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index 8f500b7c3..8f61d2cf7 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -2,38 +2,38 @@ exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `62302`; -exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `123658`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `126407`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120002`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `122745`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120193`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `122936`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5263839`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5234040`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169445`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171889`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174001`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `176869`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `180336`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `184149`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `632773`; +exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `636761`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `647136`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `651124`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `639969`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `643957`; -exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `438473`; +exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `442373`; -exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `328331`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `332144`; -exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60014`; +exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60036`; -exports[`NonfungiblePositionManager #permit owned by verifying contract gas [ @skip-on-coverage ] 1`] = `63880`; +exports[`NonfungiblePositionManager #permit owned by verifying contract gas [ @skip-on-coverage ] 1`] = `63902`; exports[`NonfungiblePositionManager #transferFrom gas [ @skip-on-coverage ] 1`] = `86323`; exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @skip-on-coverage ] 1`] = `87247`; -exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `21884`; +exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `22035`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245031`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `247656`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap index 8ba29e906..53704799a 100644 --- a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap +++ b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap @@ -2,4 +2,4 @@ exports[`PoolAddress #computeAddress gas cost [ @skip-on-coverage ] 1`] = `673`; -exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0xB01C0Cd9dD6dFf3f23939358D431299fdFFA82C8"`; +exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0xC8dF379114c2BeEdd8cBFaE53fF162cD2027ab5e"`; diff --git a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap index a2107a0d0..4b6cbc34b 100644 --- a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap +++ b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PositionValue #fees when price is above the position range gas 1`] = `51801`; +exports[`PositionValue #fees when price is above the position range gas 1`] = `53868`; -exports[`PositionValue #fees when price is below the position range gas 1`] = `51867`; +exports[`PositionValue #fees when price is below the position range gas 1`] = `53934`; -exports[`PositionValue #fees when price is within the position range gas 1`] = `57326`; +exports[`PositionValue #fees when price is within the position range gas 1`] = `60204`; exports[`PositionValue #principal gas 1`] = `26316`; -exports[`PositionValue #total gas 1`] = `60417`; +exports[`PositionValue #total gas 1`] = `63295`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap index 5fa868d57..84325df31 100644 --- a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap +++ b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap @@ -1,29 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `156167`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `157987`; -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156109`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `157893`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `247457`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `250721`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `105794`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `107424`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `90526`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `91954`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `125890`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `127520`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `156059`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `157864`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `125899`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `127529`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `126227`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `127884`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `156670`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `158499`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `156674`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `158503`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91417`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `92876`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `91513`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92935`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `91530`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92952`; diff --git a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap index 4a324c7c9..e691dc2a9 100644 --- a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap +++ b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TickLens #getClosestActiveTicks gas for almost all tick space [ @skip-on-coverage ] 1`] = `31507`; +exports[`TickLens #getClosestActiveTicks gas for almost all tick space [ @skip-on-coverage ] 1`] = `34119`; -exports[`TickLens #getNextActiveTicks gas for single populated tick [ @skip-on-coverage ] 1`] = `22142`; +exports[`TickLens #getNextActiveTicks gas for single populated tick [ @skip-on-coverage ] 1`] = `23594`; -exports[`TickLens #getPopulatedTicksInWord gas for single populated tick [ @skip-on-coverage ] 1`] = `53690`; +exports[`TickLens #getPopulatedTicksInWord gas for single populated tick [ @skip-on-coverage ] 1`] = `54876`; -exports[`TickLens fully populated word getNextActiveTicks 255 ticks [ @skip-on-coverage ] 1`] = `2460078`; +exports[`TickLens fully populated word getNextActiveTicks 255 ticks [ @skip-on-coverage ] 1`] = `2645934`; -exports[`TickLens fully populated word getNextActiveTicks 512 ticks [ @skip-on-coverage ] 1`] = `2501382`; +exports[`TickLens fully populated word getNextActiveTicks 512 ticks [ @skip-on-coverage ] 1`] = `2690142`; -exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `92513`; +exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `96603`; diff --git a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap index 00144d0d4..50541e227 100644 --- a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap +++ b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`V3Migrator #migrate gas [ @skip-on-coverage ] 1`] = `751887`; +exports[`V3Migrator #migrate gas [ @skip-on-coverage ] 1`] = `755875`; diff --git a/src/periphery/test/shared/quoter.ts b/src/periphery/test/shared/quoter.ts index 267284406..f94e69a01 100644 --- a/src/periphery/test/shared/quoter.ts +++ b/src/periphery/test/shared/quoter.ts @@ -15,7 +15,7 @@ export async function createPool( if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, deployer, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, deployer, encodePriceSqrt(1, 1), '0x'); const liquidityParams = { token0: tokenAddressA, @@ -43,7 +43,7 @@ export async function createPoolWithMultiplePositions( if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); const liquidityParams = { token0: tokenAddressA, @@ -103,7 +103,7 @@ export async function createPoolWithZeroTickInitialized( if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1), '0x'); const liquidityParams = { token0: tokenAddressA, diff --git a/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 9f26c4e36..36fe6a873 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -1,320 +1,77 @@ // SPDX-License-Identifier: BUSL-1.1 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/interfaces/IAlgebraFactory.sol'; import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolState.sol'; -import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; -import './interfaces/IAlgebraBasePluginV1.sol'; -import './interfaces/IBasePluginV1Factory.sol'; -import './interfaces/IAlgebraVirtualPool.sol'; +import './plugins/DynamicFeePlugin.sol'; +import './plugins/FarmingProxyPlugin.sol'; +import './plugins/SlidingFeePlugin.sol'; +import './plugins/VolatilityOraclePlugin.sol'; -import './libraries/VolatilityOracle.sol'; -import './libraries/AdaptiveFee.sol'; -import './types/AlgebraFeeConfigurationU144.sol'; - -/// @title Algebra Integral 1.1 default plugin -/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages -contract AlgebraBasePluginV1 is IAlgebraBasePluginV1, Timestamp, IAlgebraPlugin { +/// @title Algebra Integral 1.2 adaptive fee plugin +contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin { using Plugins for uint8; - using AlgebraFeeConfigurationU144Lib for AlgebraFeeConfiguration; - - uint256 internal constant UINT16_MODULO = 65536; - using VolatilityOracle for VolatilityOracle.Timepoint[UINT16_MODULO]; - - /// @dev The role can be granted in AlgebraFactory - bytes32 public constant ALGEBRA_BASE_PLUGIN_MANAGER = keccak256('ALGEBRA_BASE_PLUGIN_MANAGER'); /// @inheritdoc IAlgebraPlugin - uint8 public constant override defaultPluginConfig = uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG | Plugins.DYNAMIC_FEE); - - /// @inheritdoc IFarmingPlugin - address public immutable override pool; - address private immutable factory; - address private immutable pluginFactory; - - /// @inheritdoc IVolatilityOracle - VolatilityOracle.Timepoint[UINT16_MODULO] public override timepoints; - - /// @inheritdoc IVolatilityOracle - uint16 public override timepointIndex; - - /// @inheritdoc IVolatilityOracle - uint32 public override lastTimepointTimestamp; - - /// @inheritdoc IVolatilityOracle - bool public override isInitialized; - - /// @dev AlgebraFeeConfiguration struct packed in uint144 - AlgebraFeeConfigurationU144 private _feeConfig; - - /// @inheritdoc IFarmingPlugin - address public override incentive; - - /// @dev the address which connected the last incentive. Needed so that he can disconnect it - address private _lastIncentiveOwner; - - modifier onlyPool() { - _checkIfFromPool(); - _; - } - - constructor(address _pool, address _factory, address _pluginFactory) { - (factory, pool, pluginFactory) = (_factory, _pool, _pluginFactory); - } - - /// @inheritdoc IDynamicFeeManager - function feeConfig() - external - view - override - returns (uint16 alpha1, uint16 alpha2, uint32 beta1, uint32 beta2, uint16 gamma1, uint16 gamma2, uint16 baseFee) - { - (alpha1, alpha2) = (_feeConfig.alpha1(), _feeConfig.alpha2()); - (beta1, beta2) = (_feeConfig.beta1(), _feeConfig.beta2()); - (gamma1, gamma2) = (_feeConfig.gamma1(), _feeConfig.gamma2()); - baseFee = _feeConfig.baseFee(); - } - - function _checkIfFromPool() internal view { - require(msg.sender == pool, 'Only pool can call this'); - } - - function _getPoolState() internal view returns (uint160 price, int24 tick, uint16 fee, uint8 pluginConfig) { - (price, tick, fee, pluginConfig, , ) = IAlgebraPoolState(pool).globalState(); - } - - function _getPluginInPool() internal view returns (address plugin) { - return IAlgebraPool(pool).plugin(); - } - - /// @inheritdoc IAlgebraBasePluginV1 - function initialize() external override { - require(!isInitialized, 'Already initialized'); - require(_getPluginInPool() == address(this), 'Plugin not attached'); - (uint160 price, int24 tick, , ) = _getPoolState(); - require(price != 0, 'Pool is not initialized'); - - uint32 time = _blockTimestamp(); - timepoints.initialize(time, tick); - lastTimepointTimestamp = time; - isInitialized = true; - - _updatePluginConfigInPool(); - } - - // ###### Volatility and TWAP oracle ###### - - /// @inheritdoc IVolatilityOracle - function getSingleTimepoint(uint32 secondsAgo) external view override returns (int56 tickCumulative, uint88 volatilityCumulative) { - // `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared: they may differ due to interpolation errors - (, int24 tick, , ) = _getPoolState(); - uint16 lastTimepointIndex = timepointIndex; - uint16 oldestIndex = timepoints.getOldestIndex(lastTimepointIndex); - VolatilityOracle.Timepoint memory result = timepoints.getSingleTimepoint(_blockTimestamp(), secondsAgo, tick, lastTimepointIndex, oldestIndex); - (tickCumulative, volatilityCumulative) = (result.tickCumulative, result.volatilityCumulative); - } - - /// @inheritdoc IVolatilityOracle - function getTimepoints( - uint32[] memory secondsAgos - ) external view override returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives) { - // `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared: they may differ due to interpolation errors - (, int24 tick, , ) = _getPoolState(); - return timepoints.getTimepoints(_blockTimestamp(), secondsAgos, tick, timepointIndex); - } - - /// @inheritdoc IVolatilityOracle - function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external override { - require(!timepoints[startIndex].initialized); // if not initialized, then all subsequent ones too - require(amount > 0 && type(uint16).max - startIndex >= amount); - - unchecked { - for (uint256 i = startIndex; i < startIndex + amount; ++i) { - timepoints[i].blockTimestamp = 1; // will be overwritten - } - } - } - - // ###### Fee manager ###### - - /// @inheritdoc IDynamicFeeManager - function changeFeeConfiguration(AlgebraFeeConfiguration calldata _config) external override { - require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); - AdaptiveFee.validateFeeConfiguration(_config); - - _feeConfig = _config.pack(); // pack struct to uint144 and write in storage - emit FeeConfiguration(_config); - } - - /// @inheritdoc IAlgebraDynamicFeePlugin - function getCurrentFee() external view override returns (uint16 fee) { - uint16 lastIndex = timepointIndex; - AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; - if (feeConfig_.alpha1() | feeConfig_.alpha2() == 0) return feeConfig_.baseFee(); - - uint16 oldestIndex = timepoints.getOldestIndex(lastIndex); - (, int24 tick, , ) = _getPoolState(); - - uint88 volatilityAverage = timepoints.getAverageVolatility(_blockTimestamp(), tick, lastIndex, oldestIndex); - return AdaptiveFee.getFee(volatilityAverage, feeConfig_); - } - - function _getFeeAtLastTimepoint( - uint16 lastTimepointIndex, - uint16 oldestTimepointIndex, - int24 currentTick, - AlgebraFeeConfigurationU144 feeConfig_ - ) internal view returns (uint16 fee) { - if (feeConfig_.alpha1() | feeConfig_.alpha2() == 0) return feeConfig_.baseFee(); - - uint88 volatilityAverage = timepoints.getAverageVolatility(_blockTimestamp(), currentTick, lastTimepointIndex, oldestTimepointIndex); - return AdaptiveFee.getFee(volatilityAverage, feeConfig_); - } - - // ###### Farming plugin ###### - - /// @inheritdoc IFarmingPlugin - function setIncentive(address newIncentive) external override { - bool toConnect = newIncentive != address(0); - bool accessAllowed; - if (toConnect) { - accessAllowed = msg.sender == IBasePluginV1Factory(pluginFactory).farmingAddress(); - } else { - // we allow the one who connected the incentive to disconnect it, - // even if he no longer has the rights to connect incentives - if (_lastIncentiveOwner != address(0)) accessAllowed = msg.sender == _lastIncentiveOwner; - if (!accessAllowed) accessAllowed = msg.sender == IBasePluginV1Factory(pluginFactory).farmingAddress(); - } - require(accessAllowed, 'Not allowed to set incentive'); - - bool isPluginConnected = _getPluginInPool() == address(this); - if (toConnect) require(isPluginConnected, 'Plugin not attached'); - - address currentIncentive = incentive; - require(currentIncentive != newIncentive, 'Already active'); - if (toConnect) require(currentIncentive == address(0), 'Has active incentive'); + uint8 public constant override defaultPluginConfig = + uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG | Plugins.AFTER_SWAP_FLAG | Plugins.DYNAMIC_FEE); - incentive = newIncentive; - emit Incentive(newIncentive); - - if (toConnect) { - _lastIncentiveOwner = msg.sender; // write creator of this incentive - } else { - _lastIncentiveOwner = address(0); - } - - if (isPluginConnected) { - _updatePluginConfigInPool(); - } - } - - /// @inheritdoc IFarmingPlugin - function isIncentiveConnected(address targetIncentive) external view override returns (bool) { - if (incentive != targetIncentive) return false; - if (_getPluginInPool() != address(this)) return false; - (, , , uint8 pluginConfig) = _getPoolState(); - if (!pluginConfig.hasFlag(Plugins.AFTER_SWAP_FLAG)) return false; - - return true; - } + constructor(address _pool, address _factory, address _pluginFactory) BasePlugin(_pool, _factory, _pluginFactory) {} // ###### HOOKS ###### function beforeInitialize(address, uint160) external override onlyPool returns (bytes4) { - _updatePluginConfigInPool(); + _updatePluginConfigInPool(defaultPluginConfig); return IAlgebraPlugin.beforeInitialize.selector; } function afterInitialize(address, uint160, int24 tick) external override onlyPool returns (bytes4) { - uint32 _timestamp = _blockTimestamp(); - timepoints.initialize(_timestamp, tick); - - lastTimepointTimestamp = _timestamp; - isInitialized = true; + _initialize_TWAP(tick); IAlgebraPool(pool).setFee(_feeConfig.baseFee()); return IAlgebraPlugin.afterInitialize.selector; } /// @dev unused - function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external override onlyPool returns (bytes4) { - _updatePluginConfigInPool(); // should not be called, reset config - return IAlgebraPlugin.beforeModifyPosition.selector; + 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(); // should not be called, reset config + _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) { - _writeTimepointAndUpdateFee(); - return IAlgebraPlugin.beforeSwap.selector; + function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4, uint24, uint24) { + _writeTimepoint(); + uint88 volatilityAverage = _getAverageVolatilityLast(); + _updateFee(volatilityAverage); + return (IAlgebraPlugin.beforeSwap.selector, 0, 0); } function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes calldata) external override onlyPool returns (bytes4) { - address _incentive = incentive; - if (_incentive != address(0)) { - (, int24 tick, , ) = _getPoolState(); - IAlgebraVirtualPool(_incentive).crossTo(tick, zeroToOne); - } else { - _updatePluginConfigInPool(); // should not be called, reset config - } - + _updateVirtualPoolTick(zeroToOne); return IAlgebraPlugin.afterSwap.selector; } /// @dev unused function beforeFlash(address, address, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) { - _updatePluginConfigInPool(); // should not be called, reset config + _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(); // should not be called, reset config + _updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config return IAlgebraPlugin.afterFlash.selector; } - function _updatePluginConfigInPool() internal { - uint8 newPluginConfig = defaultPluginConfig; - if (incentive != address(0)) { - newPluginConfig |= uint8(Plugins.AFTER_SWAP_FLAG); - } - - (, , , uint8 currentPluginConfig) = _getPoolState(); - if (currentPluginConfig != newPluginConfig) { - IAlgebraPool(pool).setPluginConfig(newPluginConfig); - } - } - - function _writeTimepointAndUpdateFee() internal { - // single SLOAD - uint16 _lastIndex = timepointIndex; - uint32 _lastTimepointTimestamp = lastTimepointTimestamp; - AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; // struct packed in uint144 - bool _isInitialized = isInitialized; - require(_isInitialized, 'Not initialized'); - - uint32 currentTimestamp = _blockTimestamp(); - - if (_lastTimepointTimestamp == currentTimestamp) return; - - (, int24 tick, uint16 fee, ) = _getPoolState(); - (uint16 newLastIndex, uint16 newOldestIndex) = timepoints.write(_lastIndex, currentTimestamp, tick); - - timepointIndex = newLastIndex; - lastTimepointTimestamp = currentTimestamp; - - uint16 newFee = _getFeeAtLastTimepoint(newLastIndex, newOldestIndex, tick, feeConfig_); - if (newFee != fee) { - IAlgebraPool(pool).setFee(newFee); - } + 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 new file mode 100644 index 000000000..16040b4db --- /dev/null +++ b/src/plugin/contracts/AlgebraBasePluginV2.sol @@ -0,0 +1,80 @@ +// 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 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) BasePlugin(_pool, _factory, _pluginFactory) {} + + // ###### HOOKS ###### + + function beforeInitialize(address, uint160) external override onlyPool returns (bytes4) { + _updatePluginConfigInPool(defaultPluginConfig); + return IAlgebraPlugin.beforeInitialize.selector; + } + + function afterInitialize(address, uint160, int24 tick) external override onlyPool returns (bytes4) { + _initialize_TWAP(tick); + + 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 index ad294e51b..7192a2e1f 100644 --- a/src/plugin/contracts/BasePluginV1Factory.sol +++ b/src/plugin/contracts/BasePluginV1Factory.sol @@ -5,8 +5,8 @@ import './interfaces/IBasePluginV1Factory.sol'; import './libraries/AdaptiveFee.sol'; import './AlgebraBasePluginV1.sol'; -/// @title Algebra Integral 1.1 default plugin factory -/// @notice This contract creates Algebra default plugins for Algebra liquidity pools +/// @title Algebra Integral 1.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 @@ -59,7 +59,7 @@ contract BasePluginV1Factory is IBasePluginV1Factory { function _createPlugin(address pool) internal returns (address) { require(pluginByPool[pool] == address(0), 'Already created'); - IAlgebraBasePluginV1 volatilityOracle = new AlgebraBasePluginV1(pool, algebraFactory, address(this)); + IDynamicFeeManager volatilityOracle = new AlgebraBasePluginV1(pool, algebraFactory, address(this)); volatilityOracle.changeFeeConfiguration(defaultFeeConfiguration); pluginByPool[pool] = address(volatilityOracle); return address(volatilityOracle); diff --git a/src/plugin/contracts/BasePluginV2Factory.sol b/src/plugin/contracts/BasePluginV2Factory.sol new file mode 100644 index 000000000..512d34c79 --- /dev/null +++ b/src/plugin/contracts/BasePluginV2Factory.sol @@ -0,0 +1,79 @@ +// 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 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 = 500; + + /// @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)); + plugin.setBaseFee(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/base/BasePlugin.sol b/src/plugin/contracts/base/BasePlugin.sol new file mode 100644 index 000000000..1e0d63a05 --- /dev/null +++ b/src/plugin/contracts/base/BasePlugin.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BUSL-1.1 +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 default plugin +/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +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 factory; + address internal immutable pluginFactory; + + modifier onlyPool() { + _checkIfFromPool(); + _; + } + + constructor(address _pool, address _factory, address _pluginFactory) { + (factory, pool, pluginFactory) = (_factory, _pool, _pluginFactory); + } + + function _checkIfFromPool() internal view { + require(msg.sender == pool, 'Only pool can call this'); + } + + function _getPoolState() internal view returns (uint160 price, int24 tick, uint16 fee, uint8 pluginConfig) { + (price, tick, fee, pluginConfig, , ) = IAlgebraPoolState(pool).globalState(); + } + + function _getPluginInPool() internal view returns (address plugin) { + return IAlgebraPool(pool).plugin(); + } + + /// @inheritdoc IBasePlugin + function collectPluginFee(address token, uint256 amount, address recipient) external override { + require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); + 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/interfaces/IAlgebraBasePluginV1.sol b/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol deleted file mode 100644 index 7567769e9..000000000 --- a/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; -pragma abicoder v2; - -import './plugins/IVolatilityOracle.sol'; -import './plugins/IDynamicFeeManager.sol'; -import './plugins/IFarmingPlugin.sol'; - -/// @title The interface for the AlgebraBasePluginV1 -/// @notice This contract combines the standard implementations of the volatility oracle and the dynamic fee manager -/// @dev This contract stores timepoints and calculates adaptive fee and statistical averages -interface IAlgebraBasePluginV1 is IVolatilityOracle, IDynamicFeeManager, IFarmingPlugin { - /// @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; -} diff --git a/src/plugin/contracts/interfaces/IBasePlugin.sol b/src/plugin/contracts/interfaces/IBasePlugin.sol new file mode 100644 index 000000000..c31d3fecd --- /dev/null +++ b/src/plugin/contracts/interfaces/IBasePlugin.sol @@ -0,0 +1,14 @@ +// 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/IBasePluginV2Factory.sol b/src/plugin/contracts/interfaces/IBasePluginV2Factory.sol new file mode 100644 index 000000000..77597ffc3 --- /dev/null +++ b/src/plugin/contracts/interfaces/IBasePluginV2Factory.sol @@ -0,0 +1,46 @@ +// 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/IFarmingPlugin.sol b/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol index a9fceb33a..b914a99c5 100644 --- a/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol +++ b/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol @@ -8,10 +8,6 @@ interface IFarmingPlugin { /// @param newIncentive The address of the new incentive event Incentive(address newIncentive); - /// @notice Returns the address of the pool the plugin is created for - /// @return address of the pool - function pool() external view returns (address); - /// @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. @@ -29,4 +25,8 @@ interface IFarmingPlugin { /// @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 new file mode 100644 index 000000000..723cc0d5b --- /dev/null +++ b/src/plugin/contracts/interfaces/plugins/ISlidingFeePlugin.sol @@ -0,0 +1,11 @@ +// 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 index f0ab2b9d0..265f937bf 100644 --- a/src/plugin/contracts/interfaces/plugins/IVolatilityOracle.sol +++ b/src/plugin/contracts/interfaces/plugins/IVolatilityOracle.sol @@ -33,6 +33,10 @@ interface IVolatilityOracle { /// @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); diff --git a/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol b/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol index cded7f0ef..34ee2c5c1 100644 --- a/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol +++ b/src/plugin/contracts/lens/AlgebraOracleV1TWAP.sol @@ -8,7 +8,7 @@ import './IAlgebraOracleV1TWAP.sol'; import '../libraries/integration/OracleLibrary.sol'; -/// @title Algebra Integral 1.1 base plugin V1 oracle frontend +/// @title Algebra Integral 1.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 { diff --git a/src/plugin/contracts/libraries/VolatilityOracle.sol b/src/plugin/contracts/libraries/VolatilityOracle.sol index 9abd9d6b5..d65dafeb3 100644 --- a/src/plugin/contracts/libraries/VolatilityOracle.sol +++ b/src/plugin/contracts/libraries/VolatilityOracle.sol @@ -6,7 +6,6 @@ pragma solidity =0.8.20; /// @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(). -/// Version for AlgebraBasePluginV1 library VolatilityOracle { /// @notice `target` timestamp is older than oldest timepoint error targetIsTooOld(); diff --git a/src/plugin/contracts/plugins/DynamicFeePlugin.sol b/src/plugin/contracts/plugins/DynamicFeePlugin.sol new file mode 100644 index 000000000..ed8dcf679 --- /dev/null +++ b/src/plugin/contracts/plugins/DynamicFeePlugin.sol @@ -0,0 +1,73 @@ +// 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/BasePlugin.sol'; + +/// @title Algebra Integral 1.2 default plugin +/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +abstract contract DynamicFeePlugin is BasePlugin, 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; + + /// @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 { + require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); + AdaptiveFee.validateFeeConfiguration(_config); + + _feeConfig = _config.pack(); // pack struct to uint144 and write in storage + emit FeeConfiguration(_config); + } + + function _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_); + } + + function _updateFee(uint88 volatilityAverage) internal { + uint16 newFee; + AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; // struct packed in uint144 + + (, , uint16 fee, ) = _getPoolState(); + if (feeConfig_.alpha1() | feeConfig_.alpha2() == 0) { + newFee = feeConfig_.baseFee(); + } else { + newFee = AdaptiveFee.getFee(volatilityAverage, feeConfig_); + } + + if (newFee != fee) { + IAlgebraPool(pool).setFee(newFee); + } + } +} diff --git a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol new file mode 100644 index 000000000..cab8d57f5 --- /dev/null +++ b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol @@ -0,0 +1,85 @@ +// 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/BasePlugin.sol'; + +/// @title Algebra Integral 1.2 default plugin +/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +abstract contract FarmingProxyPlugin is BasePlugin, 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 new file mode 100644 index 000000000..a86a56a3a --- /dev/null +++ b/src/plugin/contracts/plugins/SlidingFeePlugin.sol @@ -0,0 +1,110 @@ +// 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 {BasePlugin} from '../base/BasePlugin.sol'; + +abstract contract SlidingFeePlugin is BasePlugin, 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 = 500; + + constructor() { + FeeFactors memory feeFactors = FeeFactors(uint128(1 << FEE_FACTOR_SHIFT), uint128(1 << FEE_FACTOR_SHIFT)); + + s_feeFactors = feeFactors; + } + + 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 { + require(IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); + + s_priceChangeFactor = newPriceChangeFactor; + + emit PriceChangeFactor(newPriceChangeFactor); + } + + function setBaseFee(uint16 newBaseFee) external override { + require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); + + 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 new file mode 100644 index 000000000..5afe8bc78 --- /dev/null +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -0,0 +1,117 @@ +// 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/BasePlugin.sol'; + +/// @title Algebra Integral 1.2 VolatilityOraclePlugin plugin +/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +abstract contract VolatilityOraclePlugin is BasePlugin, 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/MockObservable.sol b/src/plugin/contracts/test/MockObservable.sol index 3521f21e8..75af06c26 100644 --- a/src/plugin/contracts/test/MockObservable.sol +++ b/src/plugin/contracts/test/MockObservable.sol @@ -64,4 +64,8 @@ contract MockVolatilityOracle is IVolatilityOracle { 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 index 1e16573bc..1c2604c58 100644 --- a/src/plugin/contracts/test/MockPool.sol +++ b/src/plugin/contracts/test/MockPool.sol @@ -41,7 +41,7 @@ contract MockPool is IAlgebraPoolActions, IAlgebraPoolPermissionedActions, IAlge /// @inheritdoc IAlgebraPoolState int24 public override tickSpacing; /// @inheritdoc IAlgebraPoolState - uint32 public override communityFeeLastTimestamp; + uint32 public override lastFeeTransferTimestamp; /// @inheritdoc IAlgebraPoolState uint32 public override tickTreeRoot; // The root bitmap of search tree @@ -77,6 +77,11 @@ contract MockPool is IAlgebraPoolActions, IAlgebraPoolPermissionedActions, IAlge 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'); diff --git a/src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol b/src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol new file mode 100644 index 000000000..62a0c2086 --- /dev/null +++ b/src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol @@ -0,0 +1,70 @@ +// 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) AlgebraBasePluginV2(_pool, _factory, _pluginFactory) { + // + } + + 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/MockTimeDSFactoryV2.sol b/src/plugin/contracts/test/MockTimeDSFactoryV2.sol new file mode 100644 index 000000000..fdc830cdd --- /dev/null +++ b/src/plugin/contracts/test/MockTimeDSFactoryV2.sol @@ -0,0 +1,73 @@ +// 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)); + plugin.setBaseFee(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/SlidingFeeTest.sol b/src/plugin/contracts/test/SlidingFeeTest.sol new file mode 100644 index 000000000..e2d9cc886 --- /dev/null +++ b/src/plugin/contracts/test/SlidingFeeTest.sol @@ -0,0 +1,31 @@ +// 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() BasePlugin(msg.sender, msg.sender, msg.sender) {} + + 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/package-lock.json b/src/plugin/package-lock.json new file mode 100644 index 000000000..02015ec7c --- /dev/null +++ b/src/plugin/package-lock.json @@ -0,0 +1,124 @@ +{ + "name": "@cryptoalgebra/integral-base-plugin", + "version": "1.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@cryptoalgebra/integral-base-plugin", + "version": "1.2.0", + "license": "GPL-2.0-or-later", + "dependencies": { + "@cryptoalgebra/integral-core": "1.2.0", + "@cryptoalgebra/integral-periphery": "1.2.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "../core": { + "name": "@cryptoalgebra/integral-core", + "version": "1.2.0", + "license": "GPL-2.0-or-later", + "dependencies": { + "@openzeppelin/contracts": "4.9.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "../core/node_modules/@openzeppelin/contracts": { + "version": "4.9.3", + "license": "MIT" + }, + "../periphery": { + "name": "@cryptoalgebra/integral-periphery", + "version": "1.2.0", + "license": "GPL-2.0-or-later", + "dependencies": { + "@cryptoalgebra/integral-core": "1.2.0", + "@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" + } + }, + "../periphery/node_modules/@cryptoalgebra/integral-core": { + "version": "1.2.0", + "license": "GPL-2.0-or-later", + "dependencies": { + "@openzeppelin/contracts": "4.9.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "../periphery/node_modules/@openzeppelin/contracts": { + "version": "4.9.3", + "license": "MIT" + }, + "../periphery/node_modules/@uniswap/v2-core": { + "version": "1.0.1", + "license": "GPL-3.0-or-later", + "engines": { + "node": ">=10" + } + }, + "../periphery/node_modules/fast-xml-parser": { + "version": "4.3.4", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "../periphery/node_modules/is-svg": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^4.1.3" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../periphery/node_modules/strnum": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@cryptoalgebra/integral-core": { + "resolved": "../core", + "link": true + }, + "node_modules/@cryptoalgebra/integral-periphery": { + "resolved": "../periphery", + "link": true + } + } +} diff --git a/src/plugin/package.json b/src/plugin/package.json index 967f64871..78dce7ee8 100644 --- a/src/plugin/package.json +++ b/src/plugin/package.json @@ -5,7 +5,7 @@ "publishConfig": { "access": "public" }, - "version": "1.1.0", + "version": "1.2.0", "keywords": [ "algebra" ], @@ -14,8 +14,8 @@ "url": "https://github.com/cryptoalgebra/Algebra/" }, "dependencies": { - "@cryptoalgebra/integral-core": "1.1.0", - "@cryptoalgebra/integral-periphery": "1.1.0" + "@cryptoalgebra/integral-core": "1.2.0", + "@cryptoalgebra/integral-periphery": "1.2.0" }, "scripts": { "precommit": "pretty-quick --staged --pattern **/*.sol && hardhat compile", diff --git a/src/plugin/scripts/deploy.js b/src/plugin/scripts/deploy.js index fa1ac5a91..b64ac9840 100644 --- a/src/plugin/scripts/deploy.js +++ b/src/plugin/scripts/deploy.js @@ -7,8 +7,8 @@ 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); + const BasePluginV2Factory = await hre.ethers.getContractFactory("BasePluginV2Factory"); + const dsFactory = await BasePluginV2Factory.deploy(deploysData.factory); await dsFactory.waitForDeployment() @@ -19,7 +19,7 @@ async function main() { await factory.setDefaultPluginFactory(dsFactory.target) console.log('Updated plugin factory address in factory') - deploysData.BasePluginV1Factory = dsFactory.target; + deploysData.BasePluginV2Factory = dsFactory.target; fs.writeFileSync(deployDataPath, JSON.stringify(deploysData), 'utf-8'); } diff --git a/src/plugin/scripts/verify.js b/src/plugin/scripts/verify.js index 3af76260e..c9e38a23c 100644 --- a/src/plugin/scripts/verify.js +++ b/src/plugin/scripts/verify.js @@ -7,10 +7,10 @@ async function main() { const deployDataPath = path.resolve(__dirname, '../../../deploys.json'); let deploysData = JSON.parse(fs.readFileSync(deployDataPath, 'utf8')); - const BasePluginV1Factory = deploysData.BasePluginV1Factory; + const BasePluginV2Factory = deploysData.BasePluginV2Factory; await hre.run("verify:verify", { - address: BasePluginV1Factory, + address: BasePluginV2Factory, constructorArguments: [ deploysData.factory ], diff --git a/src/plugin/test/AlgebraBasePluginV1.spec.ts b/src/plugin/test/AlgebraBasePluginV1.spec.ts index a82a3cd57..b4637fc78 100644 --- a/src/plugin/test/AlgebraBasePluginV1.spec.ts +++ b/src/plugin/test/AlgebraBasePluginV1.spec.ts @@ -106,14 +106,6 @@ describe('AlgebraBasePluginV1', () => { expect((await mockPool.globalState()).pluginConfig).to.be.eq(defaultConfig); }); - it('resets config after afterSwap', async () => { - await mockPool.initialize(encodePriceSqrt(1, 1)); - await mockPool.setPluginConfig(PLUGIN_FLAGS.AFTER_SWAP_FLAG); - expect((await mockPool.globalState()).pluginConfig).to.be.eq(PLUGIN_FLAGS.AFTER_SWAP_FLAG); - await mockPool.swapToTick(100); - 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); diff --git a/src/plugin/test/AlgebraBasePluginV2.spec.ts b/src/plugin/test/AlgebraBasePluginV2.spec.ts new file mode 100644 index 000000000..6c732703d --- /dev/null +++ b/src/plugin/test/AlgebraBasePluginV2.spec.ts @@ -0,0 +1,577 @@ +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/BasePluginV2Factory.spec.ts b/src/plugin/test/BasePluginV2Factory.spec.ts new file mode 100644 index 000000000..787c6f5df --- /dev/null +++ b/src/plugin/test/BasePluginV2Factory.spec.ts @@ -0,0 +1,125 @@ +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/SlidingFee.spec.ts b/src/plugin/test/SlidingFee.spec.ts new file mode 100644 index 000000000..73cc8d4f9 --- /dev/null +++ b/src/plugin/test/SlidingFee.spec.ts @@ -0,0 +1,253 @@ +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()) 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/__snapshots__/AlgebraBasePluginV1.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap index 90337d498..f1a709f59 100644 --- a/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap +++ b/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap @@ -120,4 +120,4 @@ Array [ ] `; -exports[`AlgebraBasePluginV1 AlgebraBasePluginV1 external methods #changeFeeConfiguration feeConfig getter gas cost [ @skip-on-coverage ] 1`] = `23713`; +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 index 795dc80d1..7371eda9f 100644 --- a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -1,143 +1,143 @@ // 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`] = `207313`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee large swap crossing several initialized ticks 1`] = `216571`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `159539`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `168586`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `194977`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `204024`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `187763`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `196810`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `156028`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `165078`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `204427`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `215341`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `154382`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `165088`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `187847`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `198553`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `198781`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `209487`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `153854`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `164560`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `115281`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `117289`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `108627`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `111137`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `113427`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `115937`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `125160`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `127168`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `113035`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `115545`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `117835`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `120345`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `124718`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `126725`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `109289`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `111798`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `114089`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `116598`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #collect close to worst case 1`] = `52521`; +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`] = `70240`; +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`] = `126222`; +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`] = `280085`; +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`] = `143322`; +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`] = `151291`; +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`] = `358199`; +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`] = `168391`; +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`] = `126791`; +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`] = `354431`; +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`] = `143891`; +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`] = `61481`; +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`] = `148948`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `157999`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `148917`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `157968`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `149644`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `159753`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `165456`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `174559`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `198850`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `208109`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `149015`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `158066`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `198850`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `208109`; -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`] = `218050`; +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`] = `227309`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113415`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `128232`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `113379`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `128196`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129108`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `143977`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `163323`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `178348`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `225813`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `235071`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `156549`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `165599`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `149009`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `158060`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `148957`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `158008`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `113440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `128257`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `180214`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `189407`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `180162`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `189355`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `127545`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `142504`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `156771`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `166798`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `156740`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `166767`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `149881`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `159990`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `173516`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `183595`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `207621`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `217856`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `156838`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `166865`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `207621`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `217856`; -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`] = `226821`; +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`] = `237056`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `121238`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `137031`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `121202`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `136995`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `137168`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `153013`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `172094`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `188095`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `234584`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `244818`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `156786`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `165836`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `156832`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `166870`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `156780`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `166818`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `121263`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `137067`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `188037`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `198217`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `187985`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `198165`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `135368`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `151314`; diff --git a/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap b/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap new file mode 100644 index 000000000..08bddc64a --- /dev/null +++ b/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap @@ -0,0 +1,7 @@ +// 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/shared/fixtures.ts b/src/plugin/test/shared/fixtures.ts index a9f53da9a..2e464009b 100644 --- a/src/plugin/test/shared/fixtures.ts +++ b/src/plugin/test/shared/fixtures.ts @@ -1,5 +1,5 @@ import { ethers } from 'hardhat'; -import { MockFactory, MockPool, MockTimeAlgebraBasePluginV1, MockTimeDSFactory, BasePluginV1Factory } from '../../typechain'; +import { MockFactory, MockPool, MockTimeAlgebraBasePluginV1, MockTimeAlgebraBasePluginV2, MockTimeDSFactoryV2, MockTimeDSFactory, BasePluginV1Factory, BasePluginV2Factory } from '../../typechain'; type Fixture = () => Promise; interface MockFactoryFixture { @@ -15,8 +15,8 @@ async function mockFactoryFixture(): Promise { } interface PluginFixture extends MockFactoryFixture { - plugin: MockTimeAlgebraBasePluginV1; - mockPluginFactory: MockTimeDSFactory; + plugin: MockTimeAlgebraBasePluginV1 | MockTimeAlgebraBasePluginV2; + mockPluginFactory: MockTimeDSFactory | MockTimeDSFactoryV2; mockPool: MockPool; } @@ -49,7 +49,7 @@ export const pluginFixture: Fixture = async function (): Promise< }; interface PluginFactoryFixture extends MockFactoryFixture { - pluginFactory: BasePluginV1Factory; + pluginFactory: BasePluginV1Factory | BasePluginV2Factory; } export const pluginFactoryFixture: Fixture = async function (): Promise { @@ -63,3 +63,40 @@ export const pluginFactoryFixture: Fixture = async functio 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