From 7f7fb20588658707412b7327c5e400b18386a4cb Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 1 Jul 2024 11:45:37 +0300 Subject: [PATCH 01/60] [Core] add override and plugin fee to before swap\burn hooks --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 85 ++++++--- src/core/contracts/base/SwapCalculation.sol | 2 + .../interfaces/plugin/IAlgebraPlugin.sol | 8 +- src/core/contracts/test/MockPoolPlugin.sol | 12 +- src/core/hardhat.config.ts | 2 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 168 +++++++++--------- .../contracts/libraries/PoolAddress.sol | 2 +- 9 files changed, 161 insertions(+), 130 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index b0b6edb4e..e08edb726 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0xd5da4befe3af3bfb6a7897fcc23736d8cfe999cea43608eea29b7e414a5ae55c; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 6d25d03ea..f0086083c 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -133,18 +133,20 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio int128 liquidityDelta = -int128(amount); - _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); + (uint24 overrideFee, 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); + (amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta); - 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); @@ -153,11 +155,17 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _afterModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, amount0, amount1, data); } - function _beforeModifyPos(address owner, int24 bottomTick, int24 topTick, int128 liquidityDelta, bytes calldata data) internal { + function _beforeModifyPos( + address owner, + int24 bottomTick, + int24 topTick, + int128 liquidityDelta, + bytes calldata data + ) internal returns (uint24 overrideFee, 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 - ); + bytes4 selector; + (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data); + selector.shouldReturn(IAlgebraPlugin.beforeModifyPosition.selector); } } @@ -202,6 +210,12 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _unlock(); } + struct SwapEventParams { + uint160 currentPrice; + int24 currentTick; + uint128 currentLiquidity; + } + /// @inheritdoc IAlgebraPoolActions function swap( address recipient, @@ -210,17 +224,21 @@ 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" - (uint256 balance0Before, uint256 balance1Before) = _updateReserves(); - uint160 currentPrice; - int24 currentTick; - uint128 currentLiquidity; + SwapEventParams memory eventParams; uint256 communityFee; - (amount0, amount1, currentPrice, currentTick, currentLiquidity, communityFee) = _calculateSwap(zeroToOne, amountRequired, limitSqrtPrice); + (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, communityFee) = _calculateSwap( + overrideFee, + pluginFee, + zeroToOne, + amountRequired, + limitSqrtPrice + ); + (uint256 balance0Before, uint256 balance1Before) = _updateReserves(); if (zeroToOne) { unchecked { if (amount1 < 0) _transfer(token1, recipient, uint256(-amount1)); // amount1 cannot be > 0 @@ -237,7 +255,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _changeReserves(amount0, amount1, 0, communityFee); // reflect reserve change and pay communityFee } - _emitSwapEvent(recipient, amount0, amount1, currentPrice, currentLiquidity, currentTick); + _emitSwapEvent(recipient, amount0, amount1, eventParams.currentPrice, eventParams.currentLiquidity, eventParams.currentTick); } _unlock(); @@ -279,16 +297,20 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio 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; + SwapEventParams memory eventParams; uint256 communityFee; - (amount0, amount1, currentPrice, currentTick, currentLiquidity, communityFee) = _calculateSwap(zeroToOne, amountToSell, limitSqrtPrice); + (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, communityFee) = _calculateSwap( + overrideFee, + pluginFee, + zeroToOne, + amountToSell, + limitSqrtPrice + ); unchecked { // transfer to the recipient @@ -305,7 +327,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } } - _emitSwapEvent(recipient, amount0, amount1, currentPrice, currentLiquidity, currentTick); + _emitSwapEvent(recipient, amount0, amount1, eventParams.currentPrice, eventParams.currentLiquidity, eventParams.currentTick); _unlock(); _afterSwap(recipient, zeroToOne, amountToSell, limitSqrtPrice, amount0, amount1, data); @@ -316,11 +338,18 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio emit Swap(msg.sender, recipient, amount0, amount1, newPrice, newLiquidity, newTick); } - 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 - ); + bytes4 selector; + (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data); + selector.shouldReturn(IAlgebraPlugin.beforeSwap.selector); } } diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index 7102ff916..af570e3c0 100644 --- a/src/core/contracts/base/SwapCalculation.sol +++ b/src/core/contracts/base/SwapCalculation.sol @@ -36,6 +36,8 @@ abstract contract SwapCalculation is AlgebraPoolBase { } function _calculateSwap( + uint24 overrideFee, + uint24 pluginFee, bool zeroToOne, int256 amountRequired, uint160 limitSqrtPrice diff --git a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol index 7620d6859..fa0781aab 100644 --- a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol +++ b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol @@ -30,7 +30,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 +38,7 @@ interface IAlgebraPlugin { int24 topTick, int128 desiredLiquidityDelta, bytes calldata data - ) external returns (bytes4); + ) external returns (bytes4 selector, uint24 feeOverride, uint24 pluginFee); /// @notice The hook called after a position is modified /// @param sender The initial msg.sender for the modify position call @@ -71,7 +71,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 +80,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/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index 35a0dde34..24e6363d5 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -91,10 +91,10 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { int24 topTick, int128 desiredLiquidityDelta, bytes calldata data - ) external override returns (bytes4) { + ) external override returns (bytes4, uint24, 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, 0, 0); + return (IAlgebraPlugin.defaultPluginConfig.selector, 0, 0); } /// @notice The hook called after a position is modified @@ -126,10 +126,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, 0, 0); + return (IAlgebraPlugin.defaultPluginConfig.selector, 0, 0); } /// @notice The hook called after a swap 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/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 1e2b36314..ad666c036 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`] = `4473900`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4833685`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4473900`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4820932`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4461147`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4820932`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4461147`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22844`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21049`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index d8f911204..23d05db44 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,178 +4,178 @@ 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`] = `115333`; -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`] = `115333`; -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`] = `108692`; -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`] = `113492`; -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`] = `125212`; -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`] = `125212`; -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`] = `113100`; -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`] = `117900`; -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`] = `124769`; -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`] = `124769`; -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`] = `109353`; -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`] = `114153`; 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, two tokens 1`] = `70336`; -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`] = `126355`; -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`] = `126355`; -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`] = `280218`; -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`] = `143455`; -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`] = `151508`; -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`] = `151508`; -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`] = `358416`; -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`] = `168608`; -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`] = `126923`; -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`] = `126923`; -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`] = `354563`; -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`] = `144023`; -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`] = `61546`; -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`] = `103003`; -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`] = `102972`; -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`] = `118696`; -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`] = `152054`; -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`] = `103141`; -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`] = `152054`; -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`] = `171254`; -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`] = `103003`; -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`] = `102967`; -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`] = `119520`; -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`] = `152905`; -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`] = `171254`; -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`] = `102841`; -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`] = `102837`; -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`] = `103064`; -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`] = `103012`; -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`] = `103028`; -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 on #burn above current price burn entire position after some time passes 1`] = `115333`; -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 on #burn above current price burn when only position using ticks 1`] = `115333`; -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 on #burn above current price entire position burn but other positions are using the ticks 1`] = `108692`; -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 partial position burn 1`] = `113492`; -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 around current price burn entire position after some time passes 1`] = `125212`; -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 around current price burn when only position using ticks 1`] = `125212`; -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 around current price entire position burn but other positions are using the ticks 1`] = `113100`; -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 partial position burn 1`] = `117900`; -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 below current price burn entire position after some time passes 1`] = `124769`; -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 below current price burn when only position using ticks 1`] = `124769`; -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 below current price entire position burn but other positions are using the ticks 1`] = `109353`; -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 partial position burn 1`] = `114153`; 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 #collect close to worst case, two tokens 1`] = `70336`; -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 #mint above current price add to position after some time passes 1`] = `126355`; -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 #mint above current price add to position existing 1`] = `126355`; -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 #mint above current price new position mint first in range 1`] = `280218`; -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 second position in same range 1`] = `143455`; -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 around current price add to position after some time passes 1`] = `151508`; -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 around current price add to position existing 1`] = `151508`; -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 around current price new position mint first in range 1`] = `358416`; -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 second position in same range 1`] = `168608`; -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 below current price add to position after some time passes 1`] = `126923`; -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 below current price add to position existing 1`] = `126923`; -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 below current price new position mint first in range 1`] = `354563`; -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 second position in same range 1`] = `144023`; -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 #poke best case 1`] = `61546`; -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 #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `110826`; -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 #swapExact0For1 first swap in block with no tick movement 1`] = `103209`; -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 #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `126756`; -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, large swap crossing several initialized ticks 1`] = `160825`; -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, large swap, no initialized crossings 1`] = `110964`; -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 large swap crossing several initialized ticks after some time passes 1`] = `160825`; -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 large swap crossing several initialized ticks second time after some time passes 1`] = `180025`; -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 second swap in block moves tick, no initialized crossings 1`] = `110826`; -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 second swap in block with no tick movement 1`] = `103204`; -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 second swap in block, large swap crossing a single initialized tick 1`] = `127580`; -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, large swap crossing several initialized ticks 1`] = `161676`; -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 several large swaps with pauses 1`] = `180025`; -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 small swap after several large swaps with pauses 1`] = `103078`; -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 small swap with filled dataStorage 1`] = `103074`; -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 #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `110887`; -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 #swapExact1For0 first swap in block with no tick movement 1`] = `103249`; -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 #swapExact1For0 second swap in block with no tick movement 1`] = `103265`; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 9eec95ec1..020bdefb3 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 = 0xd5da4befe3af3bfb6a7897fcc23736d8cfe999cea43608eea29b7e414a5ae55c; /// @notice The identifying key of the pool struct PoolKey { From 7e73200bb12e42373bd413d5f2941a7ea6d8d9fb Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 1 Jul 2024 16:56:40 +0300 Subject: [PATCH 02/60] [Core] add pluginFee to _calculateSwap --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 16 ++--- src/core/contracts/base/SwapCalculation.sol | 21 ++++-- .../contracts/libraries/PriceMovementMath.sol | 2 +- .../contracts/test/PriceMovementMathTest.sol | 4 +- .../echidna/PriceMovementMathEchidnaTest.sol | 2 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +-- .../AlgebraPool.gas.spec.ts.snap | 68 +++++++++---------- .../__snapshots__/PriceMovement.spec.ts.snap | 16 ++--- .../contracts/libraries/PoolAddress.sol | 2 +- 10 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index e08edb726..3ddbfae9d 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0xd5da4befe3af3bfb6a7897fcc23736d8cfe999cea43608eea29b7e414a5ae55c; + bytes32 public constant POOL_INIT_CODE_HASH = 0xfef3bc89dde105faac795796fef422f4d6c9f20b04b4eee3f066fed753157f6d; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index f0086083c..a6d4345a3 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -230,8 +230,8 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio { // scope to prevent "stack too deep" SwapEventParams memory eventParams; - uint256 communityFee; - (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, communityFee) = _calculateSwap( + FeesAmount memory fees; + (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, fees) = _calculateSwap( overrideFee, pluginFee, zeroToOne, @@ -245,14 +245,14 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } _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); // 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); // reflect reserve change and pay communityFee } _emitSwapEvent(recipient, amount0, amount1, eventParams.currentPrice, eventParams.currentLiquidity, eventParams.currentTick); @@ -303,8 +303,8 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _updateReserves(); SwapEventParams memory eventParams; - uint256 communityFee; - (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, communityFee) = _calculateSwap( + FeesAmount memory fees; + (amount0, amount1, eventParams.currentPrice, eventParams.currentTick, eventParams.currentLiquidity, fees) = _calculateSwap( overrideFee, pluginFee, zeroToOne, @@ -318,12 +318,12 @@ 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); // 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); // reflect reserve change and pay communityFee } } diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index af570e3c0..15ceec8b6 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,18 +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); @@ -54,6 +60,7 @@ 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; if (zeroToOne) { if (limitSqrtPrice >= currentPrice || limitSqrtPrice <= TickMath.MIN_SQRT_RATIO) revert invalidLimitSqrtPrice(); @@ -93,7 +100,13 @@ abstract contract SwapCalculation is AlgebraPoolBase { if (cache.communityFee > 0) { uint256 delta = (step.feeAmount.mul(cache.communityFee)) / Constants.COMMUNITY_FEE_DENOMINATOR; step.feeAmount -= delta; - communityFeeAmount += delta; + fees.communityFeeAmount += delta; + } + + if (cache.pluginFee > 0 && cache.fee > 0) { + uint256 delta = FullMath.mulDiv(step.feeAmount, cache.pluginFee, cache.fee); + step.feeAmount -= delta; + fees.pluginFeeAmount += delta; } if (currentLiquidity > 0) cache.totalFeeGrowthInput += FullMath.mulDiv(step.feeAmount, Constants.Q128, currentLiquidity); 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/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..2e8484c41 100644 --- a/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol +++ b/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol @@ -10,7 +10,7 @@ contract PriceMovementMathEchidnaTest { uint160 sqrtPriceTargetRaw, uint128 liquidity, int256 amountRemaining, - uint16 feePips + uint24 feePips ) external pure { require(sqrtPriceRaw > 0); require(sqrtPriceTargetRaw > 0); diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index ad666c036..d3a7e7b16 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`] = `4473900`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4512453`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4473900`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4512453`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4461147`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4499700`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4461147`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4499700`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21049`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21241`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 23d05db44..7583af0a5 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -58,39 +58,39 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below curr exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61546`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103003`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103237`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `102972`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103206`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `118696`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `118982`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152054`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152496`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103141`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103375`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152054`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152496`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171254`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171696`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103003`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103237`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `102967`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103201`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119520`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119806`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `152905`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153347`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171254`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171696`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `102841`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103075`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `102837`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103071`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103064`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103298`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103012`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103246`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103028`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103262`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115333`; @@ -146,36 +146,36 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61546`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `110826`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `111060`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103209`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103443`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `126756`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `127042`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `160825`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `161267`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `110964`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `111198`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `160825`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `161267`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `180025`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `180467`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `110826`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `111060`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103204`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103438`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `127580`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `127866`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `161676`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `162118`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `180025`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `180467`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103078`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103312`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103074`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `110887`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `111121`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103249`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103483`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103265`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103499`; 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/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 020bdefb3..a2c07734c 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 = 0xd5da4befe3af3bfb6a7897fcc23736d8cfe999cea43608eea29b7e414a5ae55c; + bytes32 internal constant POOL_INIT_CODE_HASH = 0xfef3bc89dde105faac795796fef422f4d6c9f20b04b4eee3f066fed753157f6d; /// @notice The identifying key of the pool struct PoolKey { From 01455a50d11ca293b08d82e3f212d1a171c78e80 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 1 Jul 2024 18:20:20 +0300 Subject: [PATCH 03/60] [Core] add plugin pending fee to burn --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 35 ++-- src/core/contracts/base/AlgebraPoolBase.sol | 7 +- src/core/contracts/base/ReservesManager.sol | 9 +- .../interfaces/plugin/IAlgebraPlugin.sol | 2 +- src/core/contracts/test/MockPoolPlugin.sol | 6 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 176 +++++++++--------- .../contracts/libraries/PoolAddress.sol | 2 +- 9 files changed, 135 insertions(+), 114 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 3ddbfae9d..5392d0c1a 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0xfef3bc89dde105faac795796fef422f4d6c9f20b04b4eee3f066fed753157f6d; + bytes32 public constant POOL_INIT_CODE_HASH = 0x3f04fc65829e700cde446c103dab757dc068ead071efd094bb9706c7aeffc207; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index a6d4345a3..3b74e7f52 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -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,7 +133,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio int128 liquidityDelta = -int128(amount); - (uint24 overrideFee, uint24 pluginFee) = _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); + uint24 pluginFee = _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); _lock(); _updateReserves(); @@ -142,6 +142,17 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio (amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta); + if (amount0 > 0 && pluginFee > 0) { + uint256 deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR); + amount0 -= deltaPluginFeePending0; + pluginFeePending0 += uint104(deltaPluginFeePending0); + } + if (amount1 > 0 && pluginFee > 0) { + uint256 deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR); + amount1 -= deltaPluginFeePending1; + pluginFeePending1 += uint104(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 @@ -161,10 +172,10 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio int24 topTick, int128 liquidityDelta, bytes calldata data - ) internal returns (uint24 overrideFee, uint24 pluginFee) { + ) internal returns (uint24 pluginFee) { if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_POSITION_MODIFY_FLAG)) { bytes4 selector; - (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data); + (selector, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data); selector.shouldReturn(IAlgebraPlugin.beforeModifyPosition.selector); } } @@ -203,7 +214,7 @@ 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); } @@ -245,14 +256,14 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } _swapCallback(amount0, amount1, data); // callback to get tokens from the msg.sender if (balance0Before + uint256(amount0) > _balanceToken0()) revert insufficientInputAmount(); - _changeReserves(amount0, amount1, fees.communityFeeAmount, 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, fees.communityFeeAmount); // 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, eventParams.currentPrice, eventParams.currentLiquidity, eventParams.currentTick); @@ -284,13 +295,13 @@ 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; } @@ -318,12 +329,12 @@ 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, fees.communityFeeAmount, 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, fees.communityFeeAmount); // reflect reserve change and pay communityFee + _changeReserves(amount0, -leftover.toInt256(), 0, fees.communityFeeAmount, 0, fees.pluginFeeAmount); // reflect reserve change and pay communityFee } } @@ -402,7 +413,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/base/AlgebraPoolBase.sol b/src/core/contracts/base/AlgebraPoolBase.sol index 5184bdc9a..94292a7c5 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 communityFeeLastTimestamp; + + uint104 internal pluginFeePending0; + uint104 internal pluginFeePending1; /// @inheritdoc IAlgebraPoolState address public override plugin; diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index 4daf248f6..d5312de55 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -72,7 +72,14 @@ abstract contract ReservesManager is AlgebraPoolBase { /// @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 { + function _changeReserves( + int256 deltaR0, + int256 deltaR1, + uint256 communityFee0, + uint256 communityFee1, + uint256 pluginFee0, + uint256 pluginFee1 + ) internal { if (communityFee0 | communityFee1 != 0) { unchecked { // overflow is desired since we do not support tokens with totalSupply > type(uint128).max diff --git a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol index fa0781aab..c009389b5 100644 --- a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol +++ b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol @@ -38,7 +38,7 @@ interface IAlgebraPlugin { int24 topTick, int128 desiredLiquidityDelta, bytes calldata data - ) external returns (bytes4 selector, uint24 feeOverride, uint24 pluginFee); + ) 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 diff --git a/src/core/contracts/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index 24e6363d5..f67d7c8a8 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -91,10 +91,10 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { int24 topTick, int128 desiredLiquidityDelta, bytes calldata data - ) external override returns (bytes4, uint24, uint24) { + ) 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, 0, 0); - return (IAlgebraPlugin.defaultPluginConfig.selector, 0, 0); + if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.BEFORE_POSITION_MODIFY_FLAG)) return (IAlgebraPlugin.beforeModifyPosition.selector, 0); + return (IAlgebraPlugin.defaultPluginConfig.selector, 0); } /// @notice The hook called after a position is modified diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index d3a7e7b16..af2b7a597 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`] = `4512453`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4573889`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4512453`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4573889`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4499700`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4561137`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4499700`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4561137`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21241`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21547`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 7583af0a5..1460fa231 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,178 +4,178 @@ 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`] = `115333`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `115405`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115333`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115405`; -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`] = `108692`; +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`] = `108782`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113492`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113582`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125212`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125298`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125212`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125298`; -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`] = `113100`; +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`] = `113207`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `117900`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `118007`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124769`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124841`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124769`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124841`; -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`] = `109353`; +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`] = `109443`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114153`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114243`; -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`] = `52579`; -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`] = `70346`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `126355`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `126354`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `126355`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `126354`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `280218`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `280217`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `143455`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `143454`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `151508`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `151507`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `151508`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `151507`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `358416`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `358415`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `168608`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `168607`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `126923`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `126922`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `126923`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `126922`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `354563`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `354562`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `144023`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `144022`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61546`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61619`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103237`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103256`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103206`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103225`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `118982`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119001`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152496`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152515`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103375`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103394`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152496`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152515`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171696`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171715`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103237`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103256`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103201`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103220`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119806`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119825`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153347`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153366`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171696`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171715`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103075`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103094`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103071`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103090`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103298`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103317`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103246`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103265`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103262`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103281`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115333`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115405`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115333`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115405`; -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`] = `108692`; +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`] = `108782`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113492`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113582`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125212`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125298`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125212`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125298`; -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`] = `113100`; +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`] = `113207`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `117900`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `118007`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124769`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124841`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124769`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124841`; -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`] = `109353`; +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`] = `109443`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114153`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114243`; -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 #collect close to worst case 1`] = `52579`; -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 #collect close to worst case, two tokens 1`] = `70346`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `126355`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `126354`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `126355`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `126354`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `280218`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `280217`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `143455`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `143454`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `151508`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `151507`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `151508`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `151507`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `358416`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `358415`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `168608`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `168607`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `126923`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `126922`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `126923`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `126922`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `354563`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `354562`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `144023`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `144022`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61546`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61619`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `111060`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `111125`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103443`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103462`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `127042`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `127107`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `161267`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `161332`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `111198`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `111263`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `161267`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `161332`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `180467`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `180532`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `111060`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `111125`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103438`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103457`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `127866`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `127931`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `162118`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `162183`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `180467`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `180532`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103312`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103331`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103308`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103327`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `111121`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `111186`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103483`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103502`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103499`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103518`; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index a2c07734c..54aa35e5e 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 = 0xfef3bc89dde105faac795796fef422f4d6c9f20b04b4eee3f066fed753157f6d; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x3f04fc65829e700cde446c103dab757dc068ead071efd094bb9706c7aeffc207; /// @notice The identifying key of the pool struct PoolKey { From ecfde1d243a5b9e28385c675387f969fd8f353dd Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Tue, 9 Jul 2024 02:01:14 +0300 Subject: [PATCH 04/60] [Core] add pending fee update function --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 20 ++--- src/core/contracts/base/AlgebraPoolBase.sol | 2 +- src/core/contracts/base/ReservesManager.sol | 76 ++++++++++++++----- src/core/contracts/base/SwapCalculation.sol | 13 ++-- .../interfaces/pool/IAlgebraPoolErrors.sol | 2 + .../interfaces/pool/IAlgebraPoolState.sol | 6 +- src/core/contracts/libraries/Constants.sol | 2 +- .../contracts/libraries/PoolAddress.sol | 2 +- 9 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 5392d0c1a..8d88d36e0 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x3f04fc65829e700cde446c103dab757dc068ead071efd094bb9706c7aeffc207; + bytes32 public constant POOL_INIT_CODE_HASH = 0x760de329e804f32a9285a6184e9e32e7600dc829dd1e700847e31d9160b7093f; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 3b74e7f52..c82a55bb3 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -142,15 +142,17 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio (amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta); - if (amount0 > 0 && pluginFee > 0) { - uint256 deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR); - amount0 -= deltaPluginFeePending0; - pluginFeePending0 += uint104(deltaPluginFeePending0); - } - if (amount1 > 0 && pluginFee > 0) { - uint256 deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR); - amount1 -= deltaPluginFeePending1; - pluginFeePending1 += uint104(deltaPluginFeePending1); + if (pluginFee > 0) { + if (amount0 > 0) { + uint256 deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR); + amount0 -= deltaPluginFeePending0; + pluginFeePending0 += uint104(deltaPluginFeePending0); + } + if (amount1 > 0) { + uint256 deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR); + amount1 -= deltaPluginFeePending1; + pluginFeePending1 += uint104(deltaPluginFeePending1); + } } if (amount0 | amount1 != 0) { diff --git a/src/core/contracts/base/AlgebraPoolBase.sol b/src/core/contracts/base/AlgebraPoolBase.sol index 94292a7c5..530433d31 100644 --- a/src/core/contracts/base/AlgebraPoolBase.sol +++ b/src/core/contracts/base/AlgebraPoolBase.sol @@ -67,7 +67,7 @@ abstract contract AlgebraPoolBase is IAlgebraPool, Timestamp { uint104 internal communityFeePending0; uint104 internal communityFeePending1; /// @inheritdoc IAlgebraPoolState - uint32 public override communityFeeLastTimestamp; + uint32 public override lastFeeTransferTimestamp; uint104 internal pluginFeePending0; uint104 internal pluginFeePending1; diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index d5312de55..06d752a56 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -3,7 +3,6 @@ pragma solidity =0.8.20; import '../libraries/SafeCast.sol'; import './AlgebraPoolBase.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 @@ -66,6 +65,46 @@ abstract contract ReservesManager is AlgebraPoolBase { } } + function updateFeeAmounts( + int256 deltaR0, + int256 deltaR1, + uint256 fee0, + uint256 fee1, + address feesRecipient, + bytes32 slot, + uint32 lastTimestamp + ) internal returns (int256, int256) { + uint256 feePending0; + uint256 feePending1; + + assembly { + // Load the storage slot specified by the slot argument + let sl := sload(slot) + + // 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; + uint32 currentTimestamp = _blockTimestamp(); + if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY || feePending0 > type(uint104).max || feePending1 > type(uint104).max) { + if (feePending0 > 0) _transfer(token0, feesRecipient, feePending0); + if (feePending1 > 0) _transfer(token1, feesRecipient, feePending1); + (deltaR0, deltaR1) = (deltaR0 - feePending0.toInt256(), deltaR1 - feePending1.toInt256()); + (feePending0, feePending1) = (0, 0); + } + + assembly { + sstore(slot, or(or(feePending0, shl(104, feePending1)), shl(208, lastTimestamp))) + } + + return (deltaR0, deltaR1); + } + /// @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 @@ -80,30 +119,25 @@ abstract contract ReservesManager is AlgebraPoolBase { uint256 pluginFee0, uint256 pluginFee1 ) internal { + bytes32 slot; + uint32 lastTimestamp = lastFeeTransferTimestamp; + uint32 currentTimestamp = _blockTimestamp(); 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)); + assembly { + slot := communityFeePending0.slot } + (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, communityFee0, communityFee1, communityVault, slot, lastTimestamp); } + if (pluginFee0 | pluginFee1 != 0) { + assembly { + slot := pluginFeePending0.slot + } + (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); + } + + if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) lastFeeTransferTimestamp = currentTimestamp; + if (deltaR0 | deltaR1 == 0) return; (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); if (deltaR0 != 0) _reserve0 = (uint256(int256(_reserve0) + deltaR0)).toUint128(); diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index 15ceec8b6..84349cfda 100644 --- a/src/core/contracts/base/SwapCalculation.sol +++ b/src/core/contracts/base/SwapCalculation.sol @@ -48,6 +48,7 @@ abstract contract SwapCalculation is AlgebraPoolBase { int256 amountRequired, uint160 limitSqrtPrice ) internal returns (int256 amount0, int256 amount1, uint160 currentPrice, int24 currentTick, uint128 currentLiquidity, FeesAmount memory fees) { + if (pluginFee > overrideFee) revert incorrectPluginFee(); if (amountRequired == 0) revert zeroAmountRequired(); if (amountRequired == type(int256).min) revert invalidAmountRequired(); // to avoid problems when changing sign @@ -97,18 +98,18 @@ abstract contract SwapCalculation is AlgebraPoolBase { cache.amountCalculated = cache.amountCalculated.add((step.input + step.feeAmount).toInt256()); // increase calculated input amount } - if (cache.communityFee > 0) { - uint256 delta = (step.feeAmount.mul(cache.communityFee)) / Constants.COMMUNITY_FEE_DENOMINATOR; - step.feeAmount -= delta; - fees.communityFeeAmount += delta; - } - 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; + fees.communityFeeAmount += delta; + } + if (currentLiquidity > 0) cache.totalFeeGrowthInput += FullMath.mulDiv(step.feeAmount, Constants.Q128, currentLiquidity); // min or max tick can not be crossed due to limitSqrtPrice check diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol index 0f53938d6..5ffb6917f 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol @@ -25,6 +25,8 @@ interface IAlgebraPoolErrors { /// @notice Emitted if invalid amount is passed as amountRequired to swap function error invalidAmountRequired(); + error incorrectPluginFee(); + /// @notice Emitted if the pool received fewer tokens than it should have error insufficientInputAmount(); diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol index 11130fc75..6606a3f37 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol @@ -68,12 +68,12 @@ 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); 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/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 54aa35e5e..b4c3bbc70 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 = 0x3f04fc65829e700cde446c103dab757dc068ead071efd094bb9706c7aeffc207; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x760de329e804f32a9285a6184e9e32e7600dc829dd1e700847e31d9160b7093f; /// @notice The identifying key of the pool struct PoolKey { From 89ec0f887faef2f9b65bbd6fc5081a7d6f0257e8 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Tue, 9 Jul 2024 02:02:26 +0300 Subject: [PATCH 05/60] [Common] update test snapshots --- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 176 +++++++++--------- .../test/__snapshots__/Base64.spec.ts.snap | 32 ---- .../LiquidityAmounts.spec.ts.snap | 21 --- .../test/__snapshots__/Multicall.spec.ts.snap | 5 - .../__snapshots__/NFTDescriptor.spec.ts.snap | 7 - .../test/__snapshots__/NFTDescriptor.svg | 1 - .../NonfungiblePositionManager.spec.ts.snap | 26 +-- .../test/__snapshots__/Path.spec.ts.snap | 3 - .../PeripheryImmutableState.spec.ts.snap | 3 - .../__snapshots__/PoolAddress.spec.ts.snap | 5 - .../__snapshots__/PositionValue.spec.ts.snap | 11 -- .../test/__snapshots__/QuoterV2.spec.ts.snap | 29 --- .../__snapshots__/SwapRouter.spec.ts.snap | 3 - .../test/__snapshots__/TickLens.spec.ts.snap | 13 -- .../__snapshots__/V3Migrator.spec.ts.snap | 3 - 16 files changed, 106 insertions(+), 242 deletions(-) delete mode 100644 src/periphery/test/__snapshots__/Base64.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/Multicall.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/NFTDescriptor.svg delete mode 100644 src/periphery/test/__snapshots__/Path.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/PositionValue.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/TickLens.spec.ts.snap delete mode 100644 src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index af2b7a597..a70571ea8 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`] = `4573889`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4616459`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4573889`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4616459`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4561137`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4603706`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4561137`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4603706`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21547`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21759`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 1460fa231..648125c9c 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,178 +4,178 @@ 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`] = `115405`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `115440`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115405`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115440`; -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`] = `108782`; +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`] = `108825`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113582`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113625`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125298`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125332`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125298`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125332`; -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`] = `113207`; +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`] = `113250`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `118007`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `118050`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124841`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124876`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124841`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124876`; -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`] = `109443`; +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`] = `109486`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114243`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114286`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case 1`] = `52579`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case 1`] = `56975`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case, two tokens 1`] = `70346`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case, two tokens 1`] = `74742`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `126354`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `130772`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `126354`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `130772`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `280217`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `284635`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `143454`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `147872`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `151507`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `155925`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `151507`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `155925`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `358415`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `362833`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `168607`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `173025`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `126922`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `131340`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `126922`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `131340`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `354562`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `358980`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `144022`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `148440`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61619`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61662`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103256`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `107690`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103225`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `107659`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119001`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `123435`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152515`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `156949`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103394`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `107828`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152515`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `156949`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171715`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `176149`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103256`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `107690`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103220`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `107654`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119825`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `124259`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153366`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `157800`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171715`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `176149`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103094`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `107528`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103090`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `107524`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103317`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `107751`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103265`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `107699`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103281`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `107715`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115405`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115440`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115405`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115440`; -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`] = `108782`; +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`] = `108825`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113582`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113625`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125298`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125332`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125298`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125332`; -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`] = `113207`; +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`] = `113250`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `118007`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `118050`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124841`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124876`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124841`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124876`; -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`] = `109443`; +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`] = `109486`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114243`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114286`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case 1`] = `52579`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case 1`] = `56975`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case, two tokens 1`] = `70346`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case, two tokens 1`] = `74742`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `126354`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `130772`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `126354`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `130772`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `280217`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `284635`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `143454`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `147872`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `151507`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `155925`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `151507`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `155925`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `358415`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `362833`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `168607`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `173025`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `126922`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `131340`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `126922`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `131340`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `354562`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `358980`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `144022`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `148440`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61619`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61662`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `111125`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113735`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103462`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `107896`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `127107`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129717`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `161332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `163942`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `111263`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113873`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `161332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `163942`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `180532`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183142`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `111125`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113735`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103457`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `107891`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `127931`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130541`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `162183`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164793`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `180532`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183142`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103331`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `107765`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103327`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `107761`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `111186`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113796`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103502`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `107936`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103518`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `107952`; diff --git a/src/periphery/test/__snapshots__/Base64.spec.ts.snap b/src/periphery/test/__snapshots__/Base64.spec.ts.snap deleted file mode 100644 index 8753ded94..000000000 --- a/src/periphery/test/__snapshots__/Base64.spec.ts.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Base64 #encode gas cost of encode() [ @skip-on-coverage ] 1`] = `1600`; - -exports[`Base64 #encode gas cost of encode(aLpHaBeT) [ @skip-on-coverage ] 1`] = `1371`; - -exports[`Base64 #encode gas cost of encode(alphabet soup) [ @skip-on-coverage ] 1`] = `1833`; - -exports[`Base64 #encode gas cost of encode(f) [ @skip-on-coverage ] 1`] = `917`; - -exports[`Base64 #encode gas cost of encode(fo) [ @skip-on-coverage ] 1`] = `913`; - -exports[`Base64 #encode gas cost of encode(foo) [ @skip-on-coverage ] 1`] = `908`; - -exports[`Base64 #encode gas cost of encode(foob) [ @skip-on-coverage ] 1`] = `1146`; - -exports[`Base64 #encode gas cost of encode(fooba) [ @skip-on-coverage ] 1`] = `1142`; - -exports[`Base64 #encode gas cost of encode(foobar) [ @skip-on-coverage ] 1`] = `1137`; - -exports[`Base64 #encode gas cost of encode(includes -newlines) [ @skip-on-coverage ] 1`] = `2058`; - -exports[`Base64 #encode gas cost of encode(test string) [ @skip-on-coverage ] 1`] = `1600`; - -exports[`Base64 #encode gas cost of encode(this is a test) [ @skip-on-coverage ] 1`] = `1829`; - -exports[`Base64 #encode gas cost of encode(this is a very long string that should cost a lot of gas to encode :)) [ @skip-on-coverage ] 1`] = `5958`; - -exports[`Base64 #encode gas cost of encode(😀) [ @skip-on-coverage ] 1`] = `1146`; - -exports[`Base64 #encode max size string (24kB) gas cost [ @skip-on-coverage ] 1`] = `3547718`; diff --git a/src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap b/src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap deleted file mode 100644 index cb3f2d3ba..000000000 --- a/src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LiquidityAmounts #getAmount0ForLiquidity gas [ @skip-on-coverage ] 1`] = `349`; - -exports[`LiquidityAmounts #getAmountsForLiquidity gas for price above [ @skip-on-coverage ] 1`] = `478`; - -exports[`LiquidityAmounts #getAmountsForLiquidity gas for price below [ @skip-on-coverage ] 1`] = `499`; - -exports[`LiquidityAmounts #getAmountsForLiquidity gas for price inside [ @skip-on-coverage ] 1`] = `834`; - -exports[`LiquidityAmounts #getLiquidityForAmount0 gas [ @skip-on-coverage ] 1`] = `559`; - -exports[`LiquidityAmounts #getLiquidityForAmount1 gas [ @skip-on-coverage ] 1`] = `359`; - -exports[`LiquidityAmounts #getLiquidityForAmount1 gas [ @skip-on-coverage ] 2`] = `365`; - -exports[`LiquidityAmounts #getLiquidityForAmounts gas for price above [ @skip-on-coverage ] 1`] = `530`; - -exports[`LiquidityAmounts #getLiquidityForAmounts gas for price below [ @skip-on-coverage ] 1`] = `706`; - -exports[`LiquidityAmounts #getLiquidityForAmounts gas for price inside [ @skip-on-coverage ] 1`] = `1153`; diff --git a/src/periphery/test/__snapshots__/Multicall.spec.ts.snap b/src/periphery/test/__snapshots__/Multicall.spec.ts.snap deleted file mode 100644 index bd4e083b6..000000000 --- a/src/periphery/test/__snapshots__/Multicall.spec.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Multicall gas cost of pay w/ multicall [ @skip-on-coverage ] 1`] = `45894`; - -exports[`Multicall gas cost of pay w/o multicall [ @skip-on-coverage ] 1`] = `43449`; diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap deleted file mode 100644 index bf5e38ffd..000000000 --- a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NFTDescriptor #constructTokenURI gas [ @skip-on-coverage ] 1`] = `1615600`; - -exports[`NFTDescriptor #constructTokenURI snapshot matches 1`] = `"data:application/json;base64,eyJuYW1lIjoiQWxnZWJyYSAtIFVOSS9XTmF0aXZlVG9rZW4gLSAxLjAwMDA8PjEuMTA1MiIsICJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYSBsaXF1aWRpdHkgcG9zaXRpb24gaW4gYSBBbGdlYnJhIFVOSS1XTmF0aXZlVG9rZW4gcG9vbC4gVGhlIG93bmVyIG9mIHRoaXMgTkZUIGNhbiBtb2RpZnkgb3IgcmVkZWVtIHRoZSBwb3NpdGlvbi5cblxuUG9vbCBBZGRyZXNzOiAweGJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJcblVOSSBBZGRyZXNzOiAweGFiY2RlYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGZcbldOYXRpdmVUb2tlbiBBZGRyZXNzOiAweDEyMzQ1Njc4OTAxMjM0NTY3ODkxMjM0NTY3ODkwMTIzNDU2Nzg5MDFcblRva2VuIElEOiAxXG5cbuKaoO+4jyBESVNDTEFJTUVSOiBEdWUgZGlsaWdlbmNlIGlzIGltcGVyYXRpdmUgd2hlbiBhc3Nlc3NpbmcgdGhpcyBORlQuIE1ha2Ugc3VyZSB0b2tlbiBhZGRyZXNzZXMgbWF0Y2ggdGhlIGV4cGVjdGVkIHRva2VucywgYXMgdG9rZW4gc3ltYm9scyBtYXkgYmUgaW1pdGF0ZWQuIiwgImltYWdlIjogImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjNhV1IwYUQwaU1qa3dJaUJvWldsbmFIUTlJalV3TUNJZ2RtbGxkMEp2ZUQwaU1DQXdJREk1TUNBMU1EQWlJSGh0Ykc1elBTSm9kSFJ3T2k4dmQzZDNMbmN6TG05eVp5OHlNREF3TDNOMlp5SWdlRzFzYm5NNmVHeHBibXM5SjJoMGRIQTZMeTkzZDNjdWR6TXViM0puTHpFNU9Ua3ZlR3hwYm1zblBqeGtaV1p6UGp4bWFXeDBaWElnYVdROUltWXhJajQ4Wm1WSmJXRm5aU0J5WlhOMWJIUTlJbkF3SWlCNGJHbHVhenBvY21WbVBTSmtZWFJoT21sdFlXZGxMM04yWnl0NGJXdzdZbUZ6WlRZMExGQklUakphZVVJellWZFNNR0ZFTUc1TmFtdDNTbmxDYjFwWGJHNWhTRkU1U25wVmQwMURZMmRrYld4c1pEQktkbVZFTUc1TlEwRjNTVVJKTlUxRFFURk5SRUZ1U1Vob2RHSkhOWHBRVTJSdlpFaFNkMDlwT0haa00yUXpURzVqZWt4dE9YbGFlVGg1VFVSQmQwd3pUakphZVdNclVFaEtiRmt6VVdka01teHJaRWRuT1VwNlNUVk5TRUkwU25sQ2IxcFhiRzVoU0ZFNVNucFZkMDFJUWpSS2VVSnRZVmQ0YzFCVFkycFpWMHBxV2tkV2FFcDVPQ3RRUXpsNlpHMWpLeUl2UGp4bVpVbHRZV2RsSUhKbGMzVnNkRDBpY0RFaUlIaHNhVzVyT21oeVpXWTlJbVJoZEdFNmFXMWhaMlV2YzNabkszaHRiRHRpWVhObE5qUXNVRWhPTWxwNVFqTmhWMUl3WVVRd2JrMXFhM2RLZVVKdldsZHNibUZJVVRsS2VsVjNUVU5qWjJSdGJHeGtNRXAyWlVRd2JrMURRWGRKUkVrMVRVTkJNVTFFUVc1SlNHaDBZa2MxZWxCVFpHOWtTRkozVDJrNGRtUXpaRE5NYm1ONlRHMDVlVnA1T0hsTlJFRjNURE5PTWxwNVl5dFFSMDV3WTIxT2MxcFRRbXBsUkRCdVRXcFpORXA1UW1wbFZEQnVUV3BWTVVwNVFubFFVMk40VFdwQ2QyVkRZMmRhYld4ellrUXdia2w2UlhsTmVsRXhUbWxqZGxCcWQzWmpNMXB1VUdjOVBTSXZQanhtWlVsdFlXZGxJSEpsYzNWc2REMGljRElpSUhoc2FXNXJPbWh5WldZOUltUmhkR0U2YVcxaFoyVXZjM1puSzNodGJEdGlZWE5sTmpRc1VFaE9NbHA1UWpOaFYxSXdZVVF3YmsxcWEzZEtlVUp2V2xkc2JtRklVVGxLZWxWM1RVTmpaMlJ0Ykd4a01FcDJaVVF3YmsxRFFYZEpSRWsxVFVOQk1VMUVRVzVKU0doMFlrYzFlbEJUWkc5a1NGSjNUMms0ZG1RelpETk1ibU42VEcwNWVWcDVPSGxOUkVGM1RETk9NbHA1WXl0UVIwNXdZMjFPYzFwVFFtcGxSREJ1VFdwQk1rcDVRbXBsVkRCdVRWUlZlVXA1UW5sUVUyTjRUV3BDZDJWRFkyZGFiV3h6WWtRd2Jra3lXbWhaYlU1cldtbGpkbEJxZDNaak0xcHVVR2M5UFNJZ0x6NDhabVZKYldGblpTQnlaWE4xYkhROUluQXpJaUI0YkdsdWF6cG9jbVZtUFNKa1lYUmhPbWx0WVdkbEwzTjJaeXQ0Yld3N1ltRnpaVFkwTEZCSVRqSmFlVUl6WVZkU01HRkVNRzVOYW10M1NubENiMXBYYkc1aFNGRTVTbnBWZDAxRFkyZGtiV3hzWkRCS2RtVkVNRzVOUTBGM1NVUkpOVTFEUVRGTlJFRnVTVWhvZEdKSE5YcFFVMlJ2WkVoU2QwOXBPSFprTTJRelRHNWpla3h0T1hsYWVUaDVUVVJCZDB3elRqSmFlV01yVUVkT2NHTnRUbk5hVTBKcVpVUXdiazFxVVhkS2VVSnFaVlF3YmsxNlFUSktlVUo1VUZOamVFMUVRbmRsUTJObldtMXNjMkpFTUc1SmVsa3pUMFJyZDAxVFkzWlFhbmQyWXpOYWJsQm5QVDBpSUM4K1BHWmxRbXhsYm1RZ2JXOWtaVDBpYjNabGNteGhlU0lnYVc0OUluQXdJaUJwYmpJOUluQXhJaUF2UGp4bVpVSnNaVzVrSUcxdlpHVTlJbVY0WTJ4MWMybHZiaUlnYVc0eVBTSndNaUlnTHo0OFptVkNiR1Z1WkNCdGIyUmxQU0p2ZG1WeWJHRjVJaUJwYmpJOUluQXpJaUJ5WlhOMWJIUTlJbUpzWlc1a1QzVjBJaUF2UGp4bVpVZGhkWE56YVdGdVFteDFjaUJwYmowaVlteGxibVJQZFhRaUlITjBaRVJsZG1saGRHbHZiajBpTkRJaUlDOCtQQzltYVd4MFpYSStJRHhqYkdsd1VHRjBhQ0JwWkQwaVkyOXlibVZ5Y3lJK1BISmxZM1FnZDJsa2RHZzlJakk1TUNJZ2FHVnBaMmgwUFNJMU1EQWlJSEo0UFNJME1pSWdjbms5SWpReUlpQXZQand2WTJ4cGNGQmhkR2crUEhCaGRHZ2dhV1E5SW5SbGVIUXRjR0YwYUMxaElpQmtQU0pOTkRBZ01USWdTREkxTUNCQk1qZ2dNamdnTUNBd0lERWdNamM0SURRd0lGWTBOakFnUVRJNElESTRJREFnTUNBeElESTFNQ0EwT0RnZ1NEUXdJRUV5T0NBeU9DQXdJREFnTVNBeE1pQTBOakFnVmpRd0lFRXlPQ0F5T0NBd0lEQWdNU0EwTUNBeE1pQjZJaUF2UGp4d1lYUm9JR2xrUFNKdGFXNXBiV0Z3SWlCa1BTSk5Nak0wSURRME5FTXlNelFnTkRVM0xqazBPU0F5TkRJdU1qRWdORFl6SURJMU15QTBOak1pSUM4K1BHWnBiSFJsY2lCcFpEMGlkRzl3TFhKbFoybHZiaTFpYkhWeUlqNDhabVZIWVhWemMybGhia0pzZFhJZ2FXNDlJbE52ZFhKalpVZHlZWEJvYVdNaUlITjBaRVJsZG1saGRHbHZiajBpTWpRaUlDOCtQQzltYVd4MFpYSStQR3hwYm1WaGNrZHlZV1JwWlc1MElHbGtQU0puY21Ga0xYVndJaUI0TVQwaU1TSWdlREk5SWpBaUlIa3hQU0l4SWlCNU1qMGlNQ0krUEhOMGIzQWdiMlptYzJWMFBTSXdMakFpSUhOMGIzQXRZMjlzYjNJOUluZG9hWFJsSWlCemRHOXdMVzl3WVdOcGRIazlJakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l1T1NJZ2MzUnZjQzFqYjJ4dmNqMGlkMmhwZEdVaUlITjBiM0F0YjNCaFkybDBlVDBpTUNJZ0x6NDhMMnhwYm1WaGNrZHlZV1JwWlc1MFBqeHNhVzVsWVhKSGNtRmthV1Z1ZENCcFpEMGlaM0poWkMxa2IzZHVJaUI0TVQwaU1DSWdlREk5SWpFaUlIa3hQU0l3SWlCNU1qMGlNU0krUEhOMGIzQWdiMlptYzJWMFBTSXdMakFpSUhOMGIzQXRZMjlzYjNJOUluZG9hWFJsSWlCemRHOXdMVzl3WVdOcGRIazlJakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l3TGpraUlITjBiM0F0WTI5c2IzSTlJbmRvYVhSbElpQnpkRzl3TFc5d1lXTnBkSGs5SWpBaUlDOCtQQzlzYVc1bFlYSkhjbUZrYVdWdWRENDhiV0Z6YXlCcFpEMGlabUZrWlMxMWNDSWdiV0Z6YTBOdmJuUmxiblJWYm1sMGN6MGliMkpxWldOMFFtOTFibVJwYm1kQ2IzZ2lQanh5WldOMElIZHBaSFJvUFNJeElpQm9aV2xuYUhROUlqRWlJR1pwYkd3OUluVnliQ2dqWjNKaFpDMTFjQ2tpSUM4K1BDOXRZWE5yUGp4dFlYTnJJR2xrUFNKbVlXUmxMV1J2ZDI0aUlHMWhjMnREYjI1MFpXNTBWVzVwZEhNOUltOWlhbVZqZEVKdmRXNWthVzVuUW05NElqNDhjbVZqZENCM2FXUjBhRDBpTVNJZ2FHVnBaMmgwUFNJeElpQm1hV3hzUFNKMWNtd29JMmR5WVdRdFpHOTNiaWtpSUM4K1BDOXRZWE5yUGp4dFlYTnJJR2xrUFNKdWIyNWxJaUJ0WVhOclEyOXVkR1Z1ZEZWdWFYUnpQU0p2WW1wbFkzUkNiM1Z1WkdsdVowSnZlQ0krUEhKbFkzUWdkMmxrZEdnOUlqRWlJR2hsYVdkb2REMGlNU0lnWm1sc2JEMGlkMmhwZEdVaUlDOCtQQzl0WVhOclBqeHNhVzVsWVhKSGNtRmthV1Z1ZENCcFpEMGlaM0poWkMxemVXMWliMndpUGp4emRHOXdJRzltWm5ObGREMGlNQzQzSWlCemRHOXdMV052Ykc5eVBTSjNhR2wwWlNJZ2MzUnZjQzF2Y0dGamFYUjVQU0l4SWlBdlBqeHpkRzl3SUc5bVpuTmxkRDBpTGprMUlpQnpkRzl3TFdOdmJHOXlQU0ozYUdsMFpTSWdjM1J2Y0MxdmNHRmphWFI1UFNJd0lpQXZQand2YkdsdVpXRnlSM0poWkdsbGJuUStQRzFoYzJzZ2FXUTlJbVpoWkdVdGMzbHRZbTlzSWlCdFlYTnJRMjl1ZEdWdWRGVnVhWFJ6UFNKMWMyVnlVM0JoWTJWUGJsVnpaU0krUEhKbFkzUWdkMmxrZEdnOUlqSTVNSEI0SWlCb1pXbG5hSFE5SWpJd01IQjRJaUJtYVd4c1BTSjFjbXdvSTJkeVlXUXRjM2x0WW05c0tTSWdMejQ4TDIxaGMycytQQzlrWldaelBqeG5JR05zYVhBdGNHRjBhRDBpZFhKc0tDTmpiM0p1WlhKektTSStQSEpsWTNRZ1ptbHNiRDBpWVdKalpHVmhJaUI0UFNJd2NIZ2lJSGs5SWpCd2VDSWdkMmxrZEdnOUlqSTVNSEI0SWlCb1pXbG5hSFE5SWpVd01IQjRJaUF2UGp4eVpXTjBJSE4wZVd4bFBTSm1hV3gwWlhJNklIVnliQ2dqWmpFcElpQjRQU0l3Y0hnaUlIazlJakJ3ZUNJZ2QybGtkR2c5SWpJNU1IQjRJaUJvWldsbmFIUTlJalV3TUhCNElpQXZQaUE4WnlCemRIbHNaVDBpWm1sc2RHVnlPblZ5YkNnamRHOXdMWEpsWjJsdmJpMWliSFZ5S1RzZ2RISmhibk5tYjNKdE9uTmpZV3hsS0RFdU5TazdJSFJ5WVc1elptOXliUzF2Y21sbmFXNDZZMlZ1ZEdWeUlIUnZjRHNpUGp4eVpXTjBJR1pwYkd3OUltNXZibVVpSUhnOUlqQndlQ0lnZVQwaU1IQjRJaUIzYVdSMGFEMGlNamt3Y0hnaUlHaGxhV2RvZEQwaU5UQXdjSGdpSUM4K1BHVnNiR2x3YzJVZ1kzZzlJalV3SlNJZ1kzazlJakJ3ZUNJZ2NuZzlJakU0TUhCNElpQnllVDBpTVRJd2NIZ2lJR1pwYkd3OUlpTXdNREFpSUc5d1lXTnBkSGs5SWpBdU9EVWlJQzgrUEM5blBqeHlaV04wSUhnOUlqQWlJSGs5SWpBaUlIZHBaSFJvUFNJeU9UQWlJR2hsYVdkb2REMGlOVEF3SWlCeWVEMGlORElpSUhKNVBTSTBNaUlnWm1sc2JEMGljbWRpWVNnd0xEQXNNQ3d3S1NJZ2MzUnliMnRsUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERBdU1pa2lJQzgrUEM5blBqeDBaWGgwSUhSbGVIUXRjbVZ1WkdWeWFXNW5QU0p2Y0hScGJXbDZaVk53WldWa0lqNDhkR1Y0ZEZCaGRHZ2djM1JoY25SUFptWnpaWFE5SWkweE1EQWxJaUJtYVd4c1BTSjNhR2wwWlNJZ1ptOXVkQzFtWVcxcGJIazlJaWREYjNWeWFXVnlJRTVsZHljc0lHMXZibTl6Y0dGalpTSWdabTl1ZEMxemFYcGxQU0l4TUhCNElpQjRiR2x1YXpwb2NtVm1QU0lqZEdWNGRDMXdZWFJvTFdFaVBqQjRNVEl6TkRVMk56ZzVNREV5TXpRMU5qYzRPVEV5TXpRMU5qYzRPVEF4TWpNME5UWTNPRGt3TVNEaWdLSWdWMDVoZEdsMlpWUnZhMlZ1SUR4aGJtbHRZWFJsSUdGa1pHbDBhWFpsUFNKemRXMGlJR0YwZEhKcFluVjBaVTVoYldVOUluTjBZWEowVDJabWMyVjBJaUJtY205dFBTSXdKU0lnZEc4OUlqRXdNQ1VpSUdKbFoybHVQU0l3Y3lJZ1pIVnlQU0l6TUhNaUlISmxjR1ZoZEVOdmRXNTBQU0pwYm1SbFptbHVhWFJsSWlBdlBqd3ZkR1Y0ZEZCaGRHZytJRHgwWlhoMFVHRjBhQ0J6ZEdGeWRFOW1abk5sZEQwaU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IZ3hNak0wTlRZM09Ea3dNVEl6TkRVMk56ZzVNVEl6TkRVMk56ZzVNREV5TXpRMU5qYzRPVEF4SU9LQW9pQlhUbUYwYVhabFZHOXJaVzRnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K0lEd3ZkR1Y0ZEZCaGRHZytQSFJsZUhSUVlYUm9JSE4wWVhKMFQyWm1jMlYwUFNJMU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IaGhZbU5rWldGaVkyUmxabUZpWTJSbFptRmlZMlJsWm1GaVkyUmxabUZpWTJSbFptRmlZMlJtSU9LQW9pQlZUa2tnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K1BDOTBaWGgwVUdGMGFENDhkR1Y0ZEZCaGRHZ2djM1JoY25SUFptWnpaWFE5SWkwMU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IaGhZbU5rWldGaVkyUmxabUZpWTJSbFptRmlZMlJsWm1GaVkyUmxabUZpWTJSbFptRmlZMlJtSU9LQW9pQlZUa2tnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K1BDOTBaWGgwVUdGMGFENDhMM1JsZUhRK1BHY2diV0Z6YXowaWRYSnNLQ05tWVdSbExYTjViV0p2YkNraVBqeHlaV04wSUdacGJHdzlJbTV2Ym1VaUlIZzlJakJ3ZUNJZ2VUMGlNSEI0SWlCM2FXUjBhRDBpTWprd2NIZ2lJR2hsYVdkb2REMGlNakF3Y0hnaUlDOCtJRHgwWlhoMElIazlJamN3Y0hnaUlIZzlJak15Y0hnaUlHWnBiR3c5SW5kb2FYUmxJaUJtYjI1MExXWmhiV2xzZVQwaUowTnZkWEpwWlhJZ1RtVjNKeXdnYlc5dWIzTndZV05sSWlCbWIyNTBMWGRsYVdkb2REMGlNakF3SWlCbWIyNTBMWE5wZW1VOUlqTTJjSGdpUGxWT1NTOVhUbUYwYVhabFZHOXJaVzQ4TDNSbGVIUStQQzluUGp4eVpXTjBJSGc5SWpFMklpQjVQU0l4TmlJZ2QybGtkR2c5SWpJMU9DSWdhR1ZwWjJoMFBTSTBOamdpSUhKNFBTSXlOaUlnY25rOUlqSTJJaUJtYVd4c1BTSnlaMkpoS0RBc01Dd3dMREFwSWlCemRISnZhMlU5SW5KblltRW9NalUxTERJMU5Td3lOVFVzTUM0eUtTSWdMejQ4WnlCdFlYTnJQU0oxY213b0kyWmhaR1V0Wkc5M2Jpa2lJSE4wZVd4bFBTSjBjbUZ1YzJadmNtMDZkSEpoYm5Oc1lYUmxLRGN5Y0hnc01UZzVjSGdwSWo0OGNtVmpkQ0I0UFNJdE1UWndlQ0lnZVQwaUxURTJjSGdpSUhkcFpIUm9QU0l4T0RCd2VDSWdhR1ZwWjJoMFBTSXhPREJ3ZUNJZ1ptbHNiRDBpYm05dVpTSWdMejQ4Y0dGMGFDQmtQU0pOTVNBeFF6TXpJRFUzSURnNUlERXhNeUF4TkRVZ01UUTFJaUJ6ZEhKdmEyVTlJbkpuWW1Fb01Dd3dMREFzTUM0ektTSWdjM1J5YjJ0bExYZHBaSFJvUFNJek1uQjRJaUJtYVd4c1BTSnViMjVsSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJQzgrUEM5blBqeG5JRzFoYzJzOUluVnliQ2dqWm1Ga1pTMWtiM2R1S1NJZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVb056SndlQ3d4T0Rsd2VDa2lQanh5WldOMElIZzlJaTB4Tm5CNElpQjVQU0l0TVRad2VDSWdkMmxrZEdnOUlqRTRNSEI0SWlCb1pXbG5hSFE5SWpFNE1IQjRJaUJtYVd4c1BTSnViMjVsSWlBdlBqeHdZWFJvSUdROUlrMHhJREZETXpNZ05UY2dPRGtnTVRFeklERTBOU0F4TkRVaUlITjBjbTlyWlQwaWNtZGlZU2d5TlRVc01qVTFMREkxTlN3eEtTSWdabWxzYkQwaWJtOXVaU0lnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lpQXZQand2Wno0OFkybHlZMnhsSUdONFBTSTNNM0I0SWlCamVUMGlNVGt3Y0hnaUlISTlJalJ3ZUNJZ1ptbHNiRDBpZDJocGRHVWlJQzgrUEdOcGNtTnNaU0JqZUQwaU56TndlQ0lnWTNrOUlqRTVNSEI0SWlCeVBTSXlOSEI0SWlCbWFXeHNQU0p1YjI1bElpQnpkSEp2YTJVOUluZG9hWFJsSWlBdlBpQThaeUJ6ZEhsc1pUMGlkSEpoYm5ObWIzSnRPblJ5WVc1emJHRjBaU2d5T1hCNExDQXpPRFJ3ZUNraVBqeHlaV04wSUhkcFpIUm9QU0kyTTNCNElpQm9aV2xuYUhROUlqSTJjSGdpSUhKNFBTSTRjSGdpSUhKNVBTSTRjSGdpSUdacGJHdzlJbkpuWW1Fb01Dd3dMREFzTUM0MktTSWdMejQ4ZEdWNGRDQjRQU0l4TW5CNElpQjVQU0l4TjNCNElpQm1iMjUwTFdaaGJXbHNlVDBpSjBOdmRYSnBaWElnVG1WM0p5d2diVzl1YjNOd1lXTmxJaUJtYjI1MExYTnBlbVU5SWpFeWNIZ2lJR1pwYkd3OUluZG9hWFJsSWo0OGRITndZVzRnWm1sc2JEMGljbWRpWVNneU5UVXNNalUxTERJMU5Td3dMallwSWo1SlJEb2dQQzkwYzNCaGJqNHhQQzkwWlhoMFBqd3ZaejRnUEdjZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVb01qbHdlQ3dnTkRFMGNIZ3BJajQ4Y21WamRDQjNhV1IwYUQwaU1UQTFjSGdpSUdobGFXZG9kRDBpTWpad2VDSWdjbmc5SWpod2VDSWdjbms5SWpod2VDSWdabWxzYkQwaWNtZGlZU2d3TERBc01Dd3dMallwSWlBdlBqeDBaWGgwSUhnOUlqRXljSGdpSUhrOUlqRTNjSGdpSUdadmJuUXRabUZ0YVd4NVBTSW5RMjkxY21sbGNpQk9aWGNuTENCdGIyNXZjM0JoWTJVaUlHWnZiblF0YzJsNlpUMGlNVEp3ZUNJZ1ptbHNiRDBpZDJocGRHVWlQangwYzNCaGJpQm1hV3hzUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERBdU5pa2lQazFwYmlCVWFXTnJPaUE4TDNSemNHRnVQakE4TDNSbGVIUStQQzluUGlBOFp5QnpkSGxzWlQwaWRISmhibk5tYjNKdE9uUnlZVzV6YkdGMFpTZ3lPWEI0TENBME5EUndlQ2tpUGp4eVpXTjBJSGRwWkhSb1BTSXhNalp3ZUNJZ2FHVnBaMmgwUFNJeU5uQjRJaUJ5ZUQwaU9IQjRJaUJ5ZVQwaU9IQjRJaUJtYVd4c1BTSnlaMkpoS0RBc01Dd3dMREF1TmlraUlDOCtQSFJsZUhRZ2VEMGlNVEp3ZUNJZ2VUMGlNVGR3ZUNJZ1ptOXVkQzFtWVcxcGJIazlJaWREYjNWeWFXVnlJRTVsZHljc0lHMXZibTl6Y0dGalpTSWdabTl1ZEMxemFYcGxQU0l4TW5CNElpQm1hV3hzUFNKM2FHbDBaU0krUEhSemNHRnVJR1pwYkd3OUluSm5ZbUVvTWpVMUxESTFOU3d5TlRVc01DNDJLU0krVFdGNElGUnBZMnM2SUR3dmRITndZVzQrTVRBd01Ed3ZkR1Y0ZEQ0OEwyYytQR2NnYzNSNWJHVTlJblJ5WVc1elptOXliVHAwY21GdWMyeGhkR1VvTWpJMmNIZ3NJRFF6TTNCNEtTSStQSEpsWTNRZ2QybGtkR2c5SWpNMmNIZ2lJR2hsYVdkb2REMGlNelp3ZUNJZ2NuZzlJamh3ZUNJZ2NuazlJamh3ZUNJZ1ptbHNiRDBpYm05dVpTSWdjM1J5YjJ0bFBTSnlaMkpoS0RJMU5Td3lOVFVzTWpVMUxEQXVNaWtpSUM4K1BIQmhkR2dnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lpQmtQU0pOT0NBNVF6Z3VNREF3TURRZ01qSXVPVFE1TkNBeE5pNHlNRGs1SURJNElESTNJREk0SWlCbWFXeHNQU0p1YjI1bElpQnpkSEp2YTJVOUluZG9hWFJsSWlBdlBqeGphWEpqYkdVZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVelpDZ3hNM0I0TENBeU0zQjRMQ0F3Y0hncElpQmplRDBpTUhCNElpQmplVDBpTUhCNElpQnlQU0kwY0hnaUlHWnBiR3c5SW5kb2FYUmxJaTgrUEM5blBqeG5JSE4wZVd4bFBTSjBjbUZ1YzJadmNtMDZkSEpoYm5Oc1lYUmxLREl5Tm5CNExDQXpPVEp3ZUNraVBqeHlaV04wSUhkcFpIUm9QU0l6Tm5CNElpQm9aV2xuYUhROUlqTTJjSGdpSUhKNFBTSTRjSGdpSUhKNVBTSTRjSGdpSUdacGJHdzlJbTV2Ym1VaUlITjBjbTlyWlQwaWNtZGlZU2d5TlRVc01qVTFMREkxTlN3d0xqSXBJaUF2UGp4blBqeHdZWFJvSUhOMGVXeGxQU0owY21GdWMyWnZjbTA2ZEhKaGJuTnNZWFJsS0Rad2VDdzJjSGdwSWlCa1BTSk5NVElnTUV3eE1pNDJOVEl5SURrdU5UWTFPRGRNTVRnZ01TNDJNRGMzVERFekxqYzRNVGtnTVRBdU1qRTRNVXd5TWk0ek9USXpJRFpNTVRRdU5ETTBNU0F4TVM0ek5EYzRUREkwSURFeVRERTBMalF6TkRFZ01USXVOalV5TWt3eU1pNHpPVEl6SURFNFRERXpMamM0TVRrZ01UTXVOemd4T1V3eE9DQXlNaTR6T1RJelRERXlMalkxTWpJZ01UUXVORE0wTVV3eE1pQXlORXd4TVM0ek5EYzRJREUwTGpRek5ERk1OaUF5TWk0ek9USXpUREV3TGpJeE9ERWdNVE11TnpneE9Vd3hMall3TnpjZ01UaE1PUzQxTmpVNE55QXhNaTQyTlRJeVREQWdNVEpNT1M0MU5qVTROeUF4TVM0ek5EYzRUREV1TmpBM055QTJUREV3TGpJeE9ERWdNVEF1TWpFNE1VdzJJREV1TmpBM04wd3hNUzR6TkRjNElEa3VOVFkxT0RkTU1USWdNRm9pSUdacGJHdzlJbmRvYVhSbElpQXZQanhoYm1sdFlYUmxWSEpoYm5ObWIzSnRJR0YwZEhKcFluVjBaVTVoYldVOUluUnlZVzV6Wm05eWJTSWdkSGx3WlQwaWNtOTBZWFJsSWlCbWNtOXRQU0l3SURFNElERTRJaUIwYnowaU16WXdJREU0SURFNElpQmtkWEk5SWpFd2N5SWdjbVZ3WldGMFEyOTFiblE5SW1sdVpHVm1hVzVwZEdVaUx6NDhMMmMrUEM5blBqd3ZjM1puUGc9PSJ9"`; - -exports[`NFTDescriptor #svgImage matches the current snapshot 1`] = `" 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0x1234567890123456789123456789012345678901 • UNI 0x1234567890123456789123456789012345678901 • UNI UNI/WNativeToken ID: 123 Min Tick: -1000 Max Tick: 2000"`; diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.svg b/src/periphery/test/__snapshots__/NFTDescriptor.svg deleted file mode 100644 index 281d91c0b..000000000 --- a/src/periphery/test/__snapshots__/NFTDescriptor.svg +++ /dev/null @@ -1 +0,0 @@ - 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0x1234567890123456789123456789012345678901 • UNI 0x1234567890123456789123456789012345678901 • UNI UNI/WNativeToken ID: 123 Min Tick: -1000 Max Tick: 2000 \ No newline at end of file diff --git a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index bf9cc6f55..8cdeb2572 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -2,29 +2,29 @@ exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `61849`; -exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `120459`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `121048`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `116803`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `117386`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `116994`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `117577`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4813015`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4549075`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `167586`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171152`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `171677`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `175948`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `177403`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `178884`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `627918`; +exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `629574`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `641291`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `642944`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `634124`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `635777`; -exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `435931`; +exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `437499`; -exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `325789`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `327270`; exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `59310`; @@ -36,4 +36,4 @@ exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @ski exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `24267`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `241184`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `244883`; diff --git a/src/periphery/test/__snapshots__/Path.spec.ts.snap b/src/periphery/test/__snapshots__/Path.spec.ts.snap deleted file mode 100644 index 12dec0f48..000000000 --- a/src/periphery/test/__snapshots__/Path.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Path gas cost [ @skip-on-coverage ] 1`] = `254`; diff --git a/src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap b/src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap deleted file mode 100644 index a25564600..000000000 --- a/src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PeripheryImmutableState bytecode size [ @skip-on-coverage ] 1`] = `233`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap deleted file mode 100644 index 4e6653378..000000000 --- a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PoolAddress #computeAddress gas cost [ @skip-on-coverage ] 1`] = `625`; - -exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0xB01C0Cd9dD6dFf3f23939358D431299fdFFA82C8"`; diff --git a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap deleted file mode 100644 index e6ed3443f..000000000 --- a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PositionValue #fees when price is above the position range gas 1`] = `48350`; - -exports[`PositionValue #fees when price is below the position range gas 1`] = `48416`; - -exports[`PositionValue #fees when price is within the position range gas 1`] = `53875`; - -exports[`PositionValue #principal gas 1`] = `23152`; - -exports[`PositionValue #total gas 1`] = `56966`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap deleted file mode 100644 index f70c3ac2e..000000000 --- a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `155741`; - -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `155683`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `246532`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `105383`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `90115`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `125479`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `155648`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `125488`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `125816`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `156259`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `156263`; - -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `90903`; - -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `91102`; - -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `91119`; diff --git a/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap b/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap deleted file mode 100644 index 249d0d811..000000000 --- a/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SwapRouter bytecode size [ @skip-on-coverage ] 1`] = `12378`; diff --git a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap deleted file mode 100644 index 4a324c7c9..000000000 --- a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TickLens #getClosestActiveTicks gas for almost all tick space [ @skip-on-coverage ] 1`] = `31507`; - -exports[`TickLens #getNextActiveTicks gas for single populated tick [ @skip-on-coverage ] 1`] = `22142`; - -exports[`TickLens #getPopulatedTicksInWord gas for single populated tick [ @skip-on-coverage ] 1`] = `53690`; - -exports[`TickLens fully populated word getNextActiveTicks 255 ticks [ @skip-on-coverage ] 1`] = `2460078`; - -exports[`TickLens fully populated word getNextActiveTicks 512 ticks [ @skip-on-coverage ] 1`] = `2501382`; - -exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `92513`; diff --git a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap deleted file mode 100644 index d20b26114..000000000 --- a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`V3Migrator #migrate gas [ @skip-on-coverage ] 1`] = `746809`; From 80c19a62be1096137e483a00945d6e8eb19d8bf6 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Tue, 9 Jul 2024 02:17:40 +0300 Subject: [PATCH 06/60] [Plugin] adapt plugin contract --- docs/Contracts/Core/base/AlgebraPoolBase.md | 2 +- docs/Contracts/Core/interfaces/pool/IAlgebraPoolState.md | 2 +- src/plugin/contracts/AlgebraBasePluginV1.sol | 8 ++++---- src/plugin/contracts/test/MockPool.sol | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) 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/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 9f26c4e36..53fdcf616 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -242,9 +242,9 @@ contract AlgebraBasePluginV1 is IAlgebraBasePluginV1, Timestamp, IAlgebraPlugin } /// @dev unused - function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external override onlyPool returns (bytes4) { + function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external override onlyPool returns (bytes4, uint24) { _updatePluginConfigInPool(); // should not be called, reset config - return IAlgebraPlugin.beforeModifyPosition.selector; + return (IAlgebraPlugin.beforeModifyPosition.selector, 0); } /// @dev unused @@ -253,9 +253,9 @@ contract AlgebraBasePluginV1 is IAlgebraBasePluginV1, Timestamp, IAlgebraPlugin return IAlgebraPlugin.afterModifyPosition.selector; } - function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4) { + function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4, uint24, uint24) { _writeTimepointAndUpdateFee(); - return IAlgebraPlugin.beforeSwap.selector; + return (IAlgebraPlugin.beforeSwap.selector, 0, 0); } function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes calldata) external override onlyPool returns (bytes4) { diff --git a/src/plugin/contracts/test/MockPool.sol b/src/plugin/contracts/test/MockPool.sol index 1e16573bc..e191229ee 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 From 35f9f504eec32eb55147fcff7f077b5b5596d21b Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 11 Jul 2024 18:05:34 +0300 Subject: [PATCH 07/60] [Core] optimization --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/base/ReservesManager.sol | 30 ++++++++++--------- .../contracts/libraries/PoolAddress.sol | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 8d88d36e0..d2f4b587d 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x760de329e804f32a9285a6184e9e32e7600dc829dd1e700847e31d9160b7093f; + bytes32 public constant POOL_INIT_CODE_HASH = 0x7ed90ff2a054c692cc23a7254fc3d792e5e86d54a61e9b82417dfc6001eb874d; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index 06d752a56..fea298901 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -119,24 +119,26 @@ abstract contract ReservesManager is AlgebraPoolBase { uint256 pluginFee0, uint256 pluginFee1 ) internal { - bytes32 slot; - uint32 lastTimestamp = lastFeeTransferTimestamp; - uint32 currentTimestamp = _blockTimestamp(); - if (communityFee0 | communityFee1 != 0) { - assembly { - slot := communityFeePending0.slot + if (communityFee0 > 0 || communityFee1 > 0 || pluginFee0 > 0 || pluginFee1 > 0) { + bytes32 slot; + uint32 lastTimestamp = lastFeeTransferTimestamp; + uint32 currentTimestamp = _blockTimestamp(); + if (communityFee0 | communityFee1 != 0) { + assembly { + slot := communityFeePending0.slot + } + (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, communityFee0, communityFee1, communityVault, slot, lastTimestamp); } - (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, communityFee0, communityFee1, communityVault, slot, lastTimestamp); - } - if (pluginFee0 | pluginFee1 != 0) { - assembly { - slot := pluginFeePending0.slot + if (pluginFee0 | pluginFee1 != 0) { + assembly { + slot := pluginFeePending0.slot + } + (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); } - (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); - } - if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) lastFeeTransferTimestamp = currentTimestamp; + if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) lastFeeTransferTimestamp = currentTimestamp; + } if (deltaR0 | deltaR1 == 0) return; (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index b4c3bbc70..cd8488a89 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 = 0x760de329e804f32a9285a6184e9e32e7600dc829dd1e700847e31d9160b7093f; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x7ed90ff2a054c692cc23a7254fc3d792e5e86d54a61e9b82417dfc6001eb874d; /// @notice The identifying key of the pool struct PoolKey { From 876b401bbf9298c71475d3816869cd50363b47b5 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 11 Jul 2024 19:31:31 +0300 Subject: [PATCH 08/60] [Core] increase optimizer runs --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/hardhat.config.ts | 2 +- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index d2f4b587d..4cba6201a 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x7ed90ff2a054c692cc23a7254fc3d792e5e86d54a61e9b82417dfc6001eb874d; + bytes32 public constant POOL_INIT_CODE_HASH = 0x0ac47702697289e1a4e48eb16dba55f0d3da9ca5fa34e946cd034ec13de42e49; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/hardhat.config.ts b/src/core/hardhat.config.ts index ea05d8923..bf22e9b63 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: 0, + runs: 800, }, metadata: { bytecodeHash: 'none', diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index cd8488a89..f75fa003b 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 = 0x7ed90ff2a054c692cc23a7254fc3d792e5e86d54a61e9b82417dfc6001eb874d; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x0ac47702697289e1a4e48eb16dba55f0d3da9ca5fa34e946cd034ec13de42e49; /// @notice The identifying key of the pool struct PoolKey { From 37d56480c838e9b54ec5aa76101734fac3637549 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 11 Jul 2024 19:32:40 +0300 Subject: [PATCH 09/60] [Common] update snapshots --- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 176 +++++++++--------- .../test/__snapshots__/Base64.spec.ts.snap | 32 ++++ .../LiquidityAmounts.spec.ts.snap | 21 +++ .../test/__snapshots__/Multicall.spec.ts.snap | 5 + .../__snapshots__/NFTDescriptor.spec.ts.snap | 7 + .../test/__snapshots__/NFTDescriptor.svg | 1 + .../NonfungiblePositionManager.spec.ts.snap | 26 +-- .../test/__snapshots__/Path.spec.ts.snap | 3 + .../PeripheryImmutableState.spec.ts.snap | 3 + .../__snapshots__/PoolAddress.spec.ts.snap | 5 + .../__snapshots__/PositionValue.spec.ts.snap | 11 ++ .../test/__snapshots__/QuoterV2.spec.ts.snap | 29 +++ .../__snapshots__/SwapRouter.spec.ts.snap | 3 + .../test/__snapshots__/TickLens.spec.ts.snap | 13 ++ .../__snapshots__/V3Migrator.spec.ts.snap | 3 + .../AlgebraPool.gas.spec.ts.snap | 142 +++++++------- 17 files changed, 313 insertions(+), 177 deletions(-) create mode 100644 src/periphery/test/__snapshots__/Base64.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/Multicall.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/NFTDescriptor.svg create mode 100644 src/periphery/test/__snapshots__/Path.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/PositionValue.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/TickLens.spec.ts.snap create mode 100644 src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index a70571ea8..1ab463f20 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`] = `4616459`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4782479`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4616459`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4782479`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4603706`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4769727`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4603706`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4769727`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `21759`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22589`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 648125c9c..72001a6d4 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,178 +4,178 @@ 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`] = `115440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `115373`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115373`; -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`] = `108825`; +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`] = `108742`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113625`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113542`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125252`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125252`; -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`] = `113250`; +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`] = `113150`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `118050`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `117950`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124876`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124809`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124876`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124809`; -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`] = `109486`; +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`] = `109403`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114286`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114203`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case 1`] = `56975`; +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`] = `74742`; +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`] = `130772`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `126438`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `130772`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position existing 1`] = `126438`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `284635`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price new position mint first in range 1`] = `280301`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `147872`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price second position in same range 1`] = `143538`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `155925`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position after some time passes 1`] = `151591`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `155925`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price add to position existing 1`] = `151591`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `362833`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price new position mint first in range 1`] = `358499`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `173025`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint around current price second position in same range 1`] = `168691`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `131340`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position after some time passes 1`] = `127006`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `131340`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price add to position existing 1`] = `127006`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `358980`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price new position mint first in range 1`] = `354646`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `148440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `144106`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61662`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61596`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `107690`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103356`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `107659`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103325`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `123435`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119101`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `156949`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152615`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `107828`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103494`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `156949`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152615`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `176149`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171815`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `107690`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103356`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `107654`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103320`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `124259`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119925`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `157800`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153466`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `176149`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171815`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `107528`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103194`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `107524`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103190`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `107751`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103417`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `107699`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103365`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `107715`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103381`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115373`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115373`; -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`] = `108825`; +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`] = `108742`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113625`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113542`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125252`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125252`; -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`] = `113250`; +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`] = `113150`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `118050`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `117950`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124876`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124809`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124876`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124809`; -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`] = `109486`; +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`] = `109403`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114286`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114203`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case 1`] = `56975`; +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 #collect close to worst case, two tokens 1`] = `74742`; +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 add to position after some time passes 1`] = `130772`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `126438`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `130772`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position existing 1`] = `126438`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `284635`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price new position mint first in range 1`] = `280301`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `147872`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price second position in same range 1`] = `143538`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `155925`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position after some time passes 1`] = `151591`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `155925`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price add to position existing 1`] = `151591`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `362833`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price new position mint first in range 1`] = `358499`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `173025`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint around current price second position in same range 1`] = `168691`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `131340`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position after some time passes 1`] = `127006`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `131340`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price add to position existing 1`] = `127006`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `358980`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price new position mint first in range 1`] = `354646`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `148440`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `144106`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61662`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61596`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113735`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113808`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `107896`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103562`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129717`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129790`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `163942`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164015`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113873`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113946`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `163942`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164015`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183142`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183215`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113735`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113808`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `107891`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103557`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130541`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130614`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164793`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164866`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183142`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183215`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `107765`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103431`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `107761`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103427`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113796`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113880`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `107936`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103602`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `107952`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103618`; diff --git a/src/periphery/test/__snapshots__/Base64.spec.ts.snap b/src/periphery/test/__snapshots__/Base64.spec.ts.snap new file mode 100644 index 000000000..8753ded94 --- /dev/null +++ b/src/periphery/test/__snapshots__/Base64.spec.ts.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Base64 #encode gas cost of encode() [ @skip-on-coverage ] 1`] = `1600`; + +exports[`Base64 #encode gas cost of encode(aLpHaBeT) [ @skip-on-coverage ] 1`] = `1371`; + +exports[`Base64 #encode gas cost of encode(alphabet soup) [ @skip-on-coverage ] 1`] = `1833`; + +exports[`Base64 #encode gas cost of encode(f) [ @skip-on-coverage ] 1`] = `917`; + +exports[`Base64 #encode gas cost of encode(fo) [ @skip-on-coverage ] 1`] = `913`; + +exports[`Base64 #encode gas cost of encode(foo) [ @skip-on-coverage ] 1`] = `908`; + +exports[`Base64 #encode gas cost of encode(foob) [ @skip-on-coverage ] 1`] = `1146`; + +exports[`Base64 #encode gas cost of encode(fooba) [ @skip-on-coverage ] 1`] = `1142`; + +exports[`Base64 #encode gas cost of encode(foobar) [ @skip-on-coverage ] 1`] = `1137`; + +exports[`Base64 #encode gas cost of encode(includes +newlines) [ @skip-on-coverage ] 1`] = `2058`; + +exports[`Base64 #encode gas cost of encode(test string) [ @skip-on-coverage ] 1`] = `1600`; + +exports[`Base64 #encode gas cost of encode(this is a test) [ @skip-on-coverage ] 1`] = `1829`; + +exports[`Base64 #encode gas cost of encode(this is a very long string that should cost a lot of gas to encode :)) [ @skip-on-coverage ] 1`] = `5958`; + +exports[`Base64 #encode gas cost of encode(😀) [ @skip-on-coverage ] 1`] = `1146`; + +exports[`Base64 #encode max size string (24kB) gas cost [ @skip-on-coverage ] 1`] = `3547718`; diff --git a/src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap b/src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap new file mode 100644 index 000000000..cb3f2d3ba --- /dev/null +++ b/src/periphery/test/__snapshots__/LiquidityAmounts.spec.ts.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LiquidityAmounts #getAmount0ForLiquidity gas [ @skip-on-coverage ] 1`] = `349`; + +exports[`LiquidityAmounts #getAmountsForLiquidity gas for price above [ @skip-on-coverage ] 1`] = `478`; + +exports[`LiquidityAmounts #getAmountsForLiquidity gas for price below [ @skip-on-coverage ] 1`] = `499`; + +exports[`LiquidityAmounts #getAmountsForLiquidity gas for price inside [ @skip-on-coverage ] 1`] = `834`; + +exports[`LiquidityAmounts #getLiquidityForAmount0 gas [ @skip-on-coverage ] 1`] = `559`; + +exports[`LiquidityAmounts #getLiquidityForAmount1 gas [ @skip-on-coverage ] 1`] = `359`; + +exports[`LiquidityAmounts #getLiquidityForAmount1 gas [ @skip-on-coverage ] 2`] = `365`; + +exports[`LiquidityAmounts #getLiquidityForAmounts gas for price above [ @skip-on-coverage ] 1`] = `530`; + +exports[`LiquidityAmounts #getLiquidityForAmounts gas for price below [ @skip-on-coverage ] 1`] = `706`; + +exports[`LiquidityAmounts #getLiquidityForAmounts gas for price inside [ @skip-on-coverage ] 1`] = `1153`; diff --git a/src/periphery/test/__snapshots__/Multicall.spec.ts.snap b/src/periphery/test/__snapshots__/Multicall.spec.ts.snap new file mode 100644 index 000000000..bd4e083b6 --- /dev/null +++ b/src/periphery/test/__snapshots__/Multicall.spec.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Multicall gas cost of pay w/ multicall [ @skip-on-coverage ] 1`] = `45894`; + +exports[`Multicall gas cost of pay w/o multicall [ @skip-on-coverage ] 1`] = `43449`; diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap new file mode 100644 index 000000000..46f66ba69 --- /dev/null +++ b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NFTDescriptor #constructTokenURI gas [ @skip-on-coverage ] 1`] = `1616570`; + +exports[`NFTDescriptor #constructTokenURI snapshot matches 1`] = `"data:application/json;base64,eyJuYW1lIjoiQWxnZWJyYSAtIFVOSS9XTmF0aXZlVG9rZW4gLSAxLjAwMDA8PjEuMTA1MiIsICJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYSBsaXF1aWRpdHkgcG9zaXRpb24gaW4gYSBBbGdlYnJhIFVOSS1XTmF0aXZlVG9rZW4gcG9vbC4gVGhlIG93bmVyIG9mIHRoaXMgTkZUIGNhbiBtb2RpZnkgb3IgcmVkZWVtIHRoZSBwb3NpdGlvbi5cblxuUG9vbCBBZGRyZXNzOiAweGJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJcblVOSSBBZGRyZXNzOiAweGFiY2RlYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGZcbldOYXRpdmVUb2tlbiBBZGRyZXNzOiAweDEyMzQ1Njc4OTAxMjM0NTY3ODkxMjM0NTY3ODkwMTIzNDU2Nzg5MDFcblRva2VuIElEOiAxXG5cbuKaoO+4jyBESVNDTEFJTUVSOiBEdWUgZGlsaWdlbmNlIGlzIGltcGVyYXRpdmUgd2hlbiBhc3Nlc3NpbmcgdGhpcyBORlQuIE1ha2Ugc3VyZSB0b2tlbiBhZGRyZXNzZXMgbWF0Y2ggdGhlIGV4cGVjdGVkIHRva2VucywgYXMgdG9rZW4gc3ltYm9scyBtYXkgYmUgaW1pdGF0ZWQuIiwgImltYWdlIjogImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjNhV1IwYUQwaU1qa3dJaUJvWldsbmFIUTlJalV3TUNJZ2RtbGxkMEp2ZUQwaU1DQXdJREk1TUNBMU1EQWlJSGh0Ykc1elBTSm9kSFJ3T2k4dmQzZDNMbmN6TG05eVp5OHlNREF3TDNOMlp5SWdlRzFzYm5NNmVHeHBibXM5SjJoMGRIQTZMeTkzZDNjdWR6TXViM0puTHpFNU9Ua3ZlR3hwYm1zblBqeGtaV1p6UGp4bWFXeDBaWElnYVdROUltWXhJajQ4Wm1WSmJXRm5aU0J5WlhOMWJIUTlJbkF3SWlCNGJHbHVhenBvY21WbVBTSmtZWFJoT21sdFlXZGxMM04yWnl0NGJXdzdZbUZ6WlRZMExGQklUakphZVVJellWZFNNR0ZFTUc1TmFtdDNTbmxDYjFwWGJHNWhTRkU1U25wVmQwMURZMmRrYld4c1pEQktkbVZFTUc1TlEwRjNTVVJKTlUxRFFURk5SRUZ1U1Vob2RHSkhOWHBRVTJSdlpFaFNkMDlwT0haa00yUXpURzVqZWt4dE9YbGFlVGg1VFVSQmQwd3pUakphZVdNclVFaEtiRmt6VVdka01teHJaRWRuT1VwNlNUVk5TRUkwU25sQ2IxcFhiRzVoU0ZFNVNucFZkMDFJUWpSS2VVSnRZVmQ0YzFCVFkycFpWMHBxV2tkV2FFcDVPQ3RRUXpsNlpHMWpLeUl2UGp4bVpVbHRZV2RsSUhKbGMzVnNkRDBpY0RFaUlIaHNhVzVyT21oeVpXWTlJbVJoZEdFNmFXMWhaMlV2YzNabkszaHRiRHRpWVhObE5qUXNVRWhPTWxwNVFqTmhWMUl3WVVRd2JrMXFhM2RLZVVKdldsZHNibUZJVVRsS2VsVjNUVU5qWjJSdGJHeGtNRXAyWlVRd2JrMURRWGRKUkVrMVRVTkJNVTFFUVc1SlNHaDBZa2MxZWxCVFpHOWtTRkozVDJrNGRtUXpaRE5NYm1ONlRHMDVlVnA1T0hsTlJFRjNURE5PTWxwNVl5dFFSMDV3WTIxT2MxcFRRbXBsUkRCdVRXcFpORXA1UW1wbFZEQnVUV3BWTVVwNVFubFFVMk40VFdwQ2QyVkRZMmRhYld4ellrUXdia2w2UlhsTmVsRXhUbWxqZGxCcWQzWmpNMXB1VUdjOVBTSXZQanhtWlVsdFlXZGxJSEpsYzNWc2REMGljRElpSUhoc2FXNXJPbWh5WldZOUltUmhkR0U2YVcxaFoyVXZjM1puSzNodGJEdGlZWE5sTmpRc1VFaE9NbHA1UWpOaFYxSXdZVVF3YmsxcWEzZEtlVUp2V2xkc2JtRklVVGxLZWxWM1RVTmpaMlJ0Ykd4a01FcDJaVVF3YmsxRFFYZEpSRWsxVFVOQk1VMUVRVzVKU0doMFlrYzFlbEJUWkc5a1NGSjNUMms0ZG1RelpETk1ibU42VEcwNWVWcDVPSGxOUkVGM1RETk9NbHA1WXl0UVIwNXdZMjFPYzFwVFFtcGxSREJ1VFdwQk1rcDVRbXBsVkRCdVRWUlZlVXA1UW5sUVUyTjRUV3BDZDJWRFkyZGFiV3h6WWtRd2Jra3lXbWhaYlU1cldtbGpkbEJxZDNaak0xcHVVR2M5UFNJZ0x6NDhabVZKYldGblpTQnlaWE4xYkhROUluQXpJaUI0YkdsdWF6cG9jbVZtUFNKa1lYUmhPbWx0WVdkbEwzTjJaeXQ0Yld3N1ltRnpaVFkwTEZCSVRqSmFlVUl6WVZkU01HRkVNRzVOYW10M1NubENiMXBYYkc1aFNGRTVTbnBWZDAxRFkyZGtiV3hzWkRCS2RtVkVNRzVOUTBGM1NVUkpOVTFEUVRGTlJFRnVTVWhvZEdKSE5YcFFVMlJ2WkVoU2QwOXBPSFprTTJRelRHNWpla3h0T1hsYWVUaDVUVVJCZDB3elRqSmFlV01yVUVkT2NHTnRUbk5hVTBKcVpVUXdiazFxVVhkS2VVSnFaVlF3YmsxNlFUSktlVUo1VUZOamVFMUVRbmRsUTJObldtMXNjMkpFTUc1SmVsa3pUMFJyZDAxVFkzWlFhbmQyWXpOYWJsQm5QVDBpSUM4K1BHWmxRbXhsYm1RZ2JXOWtaVDBpYjNabGNteGhlU0lnYVc0OUluQXdJaUJwYmpJOUluQXhJaUF2UGp4bVpVSnNaVzVrSUcxdlpHVTlJbVY0WTJ4MWMybHZiaUlnYVc0eVBTSndNaUlnTHo0OFptVkNiR1Z1WkNCdGIyUmxQU0p2ZG1WeWJHRjVJaUJwYmpJOUluQXpJaUJ5WlhOMWJIUTlJbUpzWlc1a1QzVjBJaUF2UGp4bVpVZGhkWE56YVdGdVFteDFjaUJwYmowaVlteGxibVJQZFhRaUlITjBaRVJsZG1saGRHbHZiajBpTkRJaUlDOCtQQzltYVd4MFpYSStJRHhqYkdsd1VHRjBhQ0JwWkQwaVkyOXlibVZ5Y3lJK1BISmxZM1FnZDJsa2RHZzlJakk1TUNJZ2FHVnBaMmgwUFNJMU1EQWlJSEo0UFNJME1pSWdjbms5SWpReUlpQXZQand2WTJ4cGNGQmhkR2crUEhCaGRHZ2dhV1E5SW5SbGVIUXRjR0YwYUMxaElpQmtQU0pOTkRBZ01USWdTREkxTUNCQk1qZ2dNamdnTUNBd0lERWdNamM0SURRd0lGWTBOakFnUVRJNElESTRJREFnTUNBeElESTFNQ0EwT0RnZ1NEUXdJRUV5T0NBeU9DQXdJREFnTVNBeE1pQTBOakFnVmpRd0lFRXlPQ0F5T0NBd0lEQWdNU0EwTUNBeE1pQjZJaUF2UGp4d1lYUm9JR2xrUFNKdGFXNXBiV0Z3SWlCa1BTSk5Nak0wSURRME5FTXlNelFnTkRVM0xqazBPU0F5TkRJdU1qRWdORFl6SURJMU15QTBOak1pSUM4K1BHWnBiSFJsY2lCcFpEMGlkRzl3TFhKbFoybHZiaTFpYkhWeUlqNDhabVZIWVhWemMybGhia0pzZFhJZ2FXNDlJbE52ZFhKalpVZHlZWEJvYVdNaUlITjBaRVJsZG1saGRHbHZiajBpTWpRaUlDOCtQQzltYVd4MFpYSStQR3hwYm1WaGNrZHlZV1JwWlc1MElHbGtQU0puY21Ga0xYVndJaUI0TVQwaU1TSWdlREk5SWpBaUlIa3hQU0l4SWlCNU1qMGlNQ0krUEhOMGIzQWdiMlptYzJWMFBTSXdMakFpSUhOMGIzQXRZMjlzYjNJOUluZG9hWFJsSWlCemRHOXdMVzl3WVdOcGRIazlJakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l1T1NJZ2MzUnZjQzFqYjJ4dmNqMGlkMmhwZEdVaUlITjBiM0F0YjNCaFkybDBlVDBpTUNJZ0x6NDhMMnhwYm1WaGNrZHlZV1JwWlc1MFBqeHNhVzVsWVhKSGNtRmthV1Z1ZENCcFpEMGlaM0poWkMxa2IzZHVJaUI0TVQwaU1DSWdlREk5SWpFaUlIa3hQU0l3SWlCNU1qMGlNU0krUEhOMGIzQWdiMlptYzJWMFBTSXdMakFpSUhOMGIzQXRZMjlzYjNJOUluZG9hWFJsSWlCemRHOXdMVzl3WVdOcGRIazlJakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l3TGpraUlITjBiM0F0WTI5c2IzSTlJbmRvYVhSbElpQnpkRzl3TFc5d1lXTnBkSGs5SWpBaUlDOCtQQzlzYVc1bFlYSkhjbUZrYVdWdWRENDhiV0Z6YXlCcFpEMGlabUZrWlMxMWNDSWdiV0Z6YTBOdmJuUmxiblJWYm1sMGN6MGliMkpxWldOMFFtOTFibVJwYm1kQ2IzZ2lQanh5WldOMElIZHBaSFJvUFNJeElpQm9aV2xuYUhROUlqRWlJR1pwYkd3OUluVnliQ2dqWjNKaFpDMTFjQ2tpSUM4K1BDOXRZWE5yUGp4dFlYTnJJR2xrUFNKbVlXUmxMV1J2ZDI0aUlHMWhjMnREYjI1MFpXNTBWVzVwZEhNOUltOWlhbVZqZEVKdmRXNWthVzVuUW05NElqNDhjbVZqZENCM2FXUjBhRDBpTVNJZ2FHVnBaMmgwUFNJeElpQm1hV3hzUFNKMWNtd29JMmR5WVdRdFpHOTNiaWtpSUM4K1BDOXRZWE5yUGp4dFlYTnJJR2xrUFNKdWIyNWxJaUJ0WVhOclEyOXVkR1Z1ZEZWdWFYUnpQU0p2WW1wbFkzUkNiM1Z1WkdsdVowSnZlQ0krUEhKbFkzUWdkMmxrZEdnOUlqRWlJR2hsYVdkb2REMGlNU0lnWm1sc2JEMGlkMmhwZEdVaUlDOCtQQzl0WVhOclBqeHNhVzVsWVhKSGNtRmthV1Z1ZENCcFpEMGlaM0poWkMxemVXMWliMndpUGp4emRHOXdJRzltWm5ObGREMGlNQzQzSWlCemRHOXdMV052Ykc5eVBTSjNhR2wwWlNJZ2MzUnZjQzF2Y0dGamFYUjVQU0l4SWlBdlBqeHpkRzl3SUc5bVpuTmxkRDBpTGprMUlpQnpkRzl3TFdOdmJHOXlQU0ozYUdsMFpTSWdjM1J2Y0MxdmNHRmphWFI1UFNJd0lpQXZQand2YkdsdVpXRnlSM0poWkdsbGJuUStQRzFoYzJzZ2FXUTlJbVpoWkdVdGMzbHRZbTlzSWlCdFlYTnJRMjl1ZEdWdWRGVnVhWFJ6UFNKMWMyVnlVM0JoWTJWUGJsVnpaU0krUEhKbFkzUWdkMmxrZEdnOUlqSTVNSEI0SWlCb1pXbG5hSFE5SWpJd01IQjRJaUJtYVd4c1BTSjFjbXdvSTJkeVlXUXRjM2x0WW05c0tTSWdMejQ4TDIxaGMycytQQzlrWldaelBqeG5JR05zYVhBdGNHRjBhRDBpZFhKc0tDTmpiM0p1WlhKektTSStQSEpsWTNRZ1ptbHNiRDBpWVdKalpHVmhJaUI0UFNJd2NIZ2lJSGs5SWpCd2VDSWdkMmxrZEdnOUlqSTVNSEI0SWlCb1pXbG5hSFE5SWpVd01IQjRJaUF2UGp4eVpXTjBJSE4wZVd4bFBTSm1hV3gwWlhJNklIVnliQ2dqWmpFcElpQjRQU0l3Y0hnaUlIazlJakJ3ZUNJZ2QybGtkR2c5SWpJNU1IQjRJaUJvWldsbmFIUTlJalV3TUhCNElpQXZQaUE4WnlCemRIbHNaVDBpWm1sc2RHVnlPblZ5YkNnamRHOXdMWEpsWjJsdmJpMWliSFZ5S1RzZ2RISmhibk5tYjNKdE9uTmpZV3hsS0RFdU5TazdJSFJ5WVc1elptOXliUzF2Y21sbmFXNDZZMlZ1ZEdWeUlIUnZjRHNpUGp4eVpXTjBJR1pwYkd3OUltNXZibVVpSUhnOUlqQndlQ0lnZVQwaU1IQjRJaUIzYVdSMGFEMGlNamt3Y0hnaUlHaGxhV2RvZEQwaU5UQXdjSGdpSUM4K1BHVnNiR2x3YzJVZ1kzZzlJalV3SlNJZ1kzazlJakJ3ZUNJZ2NuZzlJakU0TUhCNElpQnllVDBpTVRJd2NIZ2lJR1pwYkd3OUlpTXdNREFpSUc5d1lXTnBkSGs5SWpBdU9EVWlJQzgrUEM5blBqeHlaV04wSUhnOUlqQWlJSGs5SWpBaUlIZHBaSFJvUFNJeU9UQWlJR2hsYVdkb2REMGlOVEF3SWlCeWVEMGlORElpSUhKNVBTSTBNaUlnWm1sc2JEMGljbWRpWVNnd0xEQXNNQ3d3S1NJZ2MzUnliMnRsUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERBdU1pa2lJQzgrUEM5blBqeDBaWGgwSUhSbGVIUXRjbVZ1WkdWeWFXNW5QU0p2Y0hScGJXbDZaVk53WldWa0lqNDhkR1Y0ZEZCaGRHZ2djM1JoY25SUFptWnpaWFE5SWkweE1EQWxJaUJtYVd4c1BTSjNhR2wwWlNJZ1ptOXVkQzFtWVcxcGJIazlJaWREYjNWeWFXVnlJRTVsZHljc0lHMXZibTl6Y0dGalpTSWdabTl1ZEMxemFYcGxQU0l4TUhCNElpQjRiR2x1YXpwb2NtVm1QU0lqZEdWNGRDMXdZWFJvTFdFaVBqQjRNVEl6TkRVMk56ZzVNREV5TXpRMU5qYzRPVEV5TXpRMU5qYzRPVEF4TWpNME5UWTNPRGt3TVNEaWdLSWdWMDVoZEdsMlpWUnZhMlZ1SUR4aGJtbHRZWFJsSUdGa1pHbDBhWFpsUFNKemRXMGlJR0YwZEhKcFluVjBaVTVoYldVOUluTjBZWEowVDJabWMyVjBJaUJtY205dFBTSXdKU0lnZEc4OUlqRXdNQ1VpSUdKbFoybHVQU0l3Y3lJZ1pIVnlQU0l6TUhNaUlISmxjR1ZoZEVOdmRXNTBQU0pwYm1SbFptbHVhWFJsSWlBdlBqd3ZkR1Y0ZEZCaGRHZytJRHgwWlhoMFVHRjBhQ0J6ZEdGeWRFOW1abk5sZEQwaU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IZ3hNak0wTlRZM09Ea3dNVEl6TkRVMk56ZzVNVEl6TkRVMk56ZzVNREV5TXpRMU5qYzRPVEF4SU9LQW9pQlhUbUYwYVhabFZHOXJaVzRnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K0lEd3ZkR1Y0ZEZCaGRHZytQSFJsZUhSUVlYUm9JSE4wWVhKMFQyWm1jMlYwUFNJMU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IaGhZbU5rWldGaVkyUmxabUZpWTJSbFptRmlZMlJsWm1GaVkyUmxabUZpWTJSbFptRmlZMlJtSU9LQW9pQlZUa2tnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K1BDOTBaWGgwVUdGMGFENDhkR1Y0ZEZCaGRHZ2djM1JoY25SUFptWnpaWFE5SWkwMU1DVWlJR1pwYkd3OUluZG9hWFJsSWlCbWIyNTBMV1poYldsc2VUMGlKME52ZFhKcFpYSWdUbVYzSnl3Z2JXOXViM053WVdObElpQm1iMjUwTFhOcGVtVTlJakV3Y0hnaUlIaHNhVzVyT21oeVpXWTlJaU4wWlhoMExYQmhkR2d0WVNJK01IaGhZbU5rWldGaVkyUmxabUZpWTJSbFptRmlZMlJsWm1GaVkyUmxabUZpWTJSbFptRmlZMlJtSU9LQW9pQlZUa2tnUEdGdWFXMWhkR1VnWVdSa2FYUnBkbVU5SW5OMWJTSWdZWFIwY21saWRYUmxUbUZ0WlQwaWMzUmhjblJQWm1aelpYUWlJR1p5YjIwOUlqQWxJaUIwYnowaU1UQXdKU0lnWW1WbmFXNDlJakJ6SWlCa2RYSTlJak13Y3lJZ2NtVndaV0YwUTI5MWJuUTlJbWx1WkdWbWFXNXBkR1VpSUM4K1BDOTBaWGgwVUdGMGFENDhMM1JsZUhRK1BHY2diV0Z6YXowaWRYSnNLQ05tWVdSbExYTjViV0p2YkNraVBqeHlaV04wSUdacGJHdzlJbTV2Ym1VaUlIZzlJakJ3ZUNJZ2VUMGlNSEI0SWlCM2FXUjBhRDBpTWprd2NIZ2lJR2hsYVdkb2REMGlNakF3Y0hnaUlDOCtJRHgwWlhoMElIazlJamN3Y0hnaUlIZzlJak15Y0hnaUlHWnBiR3c5SW5kb2FYUmxJaUJtYjI1MExXWmhiV2xzZVQwaUowTnZkWEpwWlhJZ1RtVjNKeXdnYlc5dWIzTndZV05sSWlCbWIyNTBMWGRsYVdkb2REMGlNakF3SWlCbWIyNTBMWE5wZW1VOUlqTTJjSGdpUGxWT1NTOVhUbUYwYVhabFZHOXJaVzQ4TDNSbGVIUStQQzluUGp4eVpXTjBJSGc5SWpFMklpQjVQU0l4TmlJZ2QybGtkR2c5SWpJMU9DSWdhR1ZwWjJoMFBTSTBOamdpSUhKNFBTSXlOaUlnY25rOUlqSTJJaUJtYVd4c1BTSnlaMkpoS0RBc01Dd3dMREFwSWlCemRISnZhMlU5SW5KblltRW9NalUxTERJMU5Td3lOVFVzTUM0eUtTSWdMejQ4WnlCdFlYTnJQU0oxY213b0kyWmhaR1V0Wkc5M2Jpa2lJSE4wZVd4bFBTSjBjbUZ1YzJadmNtMDZkSEpoYm5Oc1lYUmxLRGN5Y0hnc01UZzVjSGdwSWo0OGNtVmpkQ0I0UFNJdE1UWndlQ0lnZVQwaUxURTJjSGdpSUhkcFpIUm9QU0l4T0RCd2VDSWdhR1ZwWjJoMFBTSXhPREJ3ZUNJZ1ptbHNiRDBpYm05dVpTSWdMejQ4Y0dGMGFDQmtQU0pOTVNBeFF6TXpJRFUzSURnNUlERXhNeUF4TkRVZ01UUTFJaUJ6ZEhKdmEyVTlJbkpuWW1Fb01Dd3dMREFzTUM0ektTSWdjM1J5YjJ0bExYZHBaSFJvUFNJek1uQjRJaUJtYVd4c1BTSnViMjVsSWlCemRISnZhMlV0YkdsdVpXTmhjRDBpY205MWJtUWlJQzgrUEM5blBqeG5JRzFoYzJzOUluVnliQ2dqWm1Ga1pTMWtiM2R1S1NJZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVb056SndlQ3d4T0Rsd2VDa2lQanh5WldOMElIZzlJaTB4Tm5CNElpQjVQU0l0TVRad2VDSWdkMmxrZEdnOUlqRTRNSEI0SWlCb1pXbG5hSFE5SWpFNE1IQjRJaUJtYVd4c1BTSnViMjVsSWlBdlBqeHdZWFJvSUdROUlrMHhJREZETXpNZ05UY2dPRGtnTVRFeklERTBOU0F4TkRVaUlITjBjbTlyWlQwaWNtZGlZU2d5TlRVc01qVTFMREkxTlN3eEtTSWdabWxzYkQwaWJtOXVaU0lnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lpQXZQand2Wno0OFkybHlZMnhsSUdONFBTSTNNM0I0SWlCamVUMGlNVGt3Y0hnaUlISTlJalJ3ZUNJZ1ptbHNiRDBpZDJocGRHVWlJQzgrUEdOcGNtTnNaU0JqZUQwaU56TndlQ0lnWTNrOUlqRTVNSEI0SWlCeVBTSXlOSEI0SWlCbWFXeHNQU0p1YjI1bElpQnpkSEp2YTJVOUluZG9hWFJsSWlBdlBpQThaeUJ6ZEhsc1pUMGlkSEpoYm5ObWIzSnRPblJ5WVc1emJHRjBaU2d5T1hCNExDQXpPRFJ3ZUNraVBqeHlaV04wSUhkcFpIUm9QU0kyTTNCNElpQm9aV2xuYUhROUlqSTJjSGdpSUhKNFBTSTRjSGdpSUhKNVBTSTRjSGdpSUdacGJHdzlJbkpuWW1Fb01Dd3dMREFzTUM0MktTSWdMejQ4ZEdWNGRDQjRQU0l4TW5CNElpQjVQU0l4TjNCNElpQm1iMjUwTFdaaGJXbHNlVDBpSjBOdmRYSnBaWElnVG1WM0p5d2diVzl1YjNOd1lXTmxJaUJtYjI1MExYTnBlbVU5SWpFeWNIZ2lJR1pwYkd3OUluZG9hWFJsSWo0OGRITndZVzRnWm1sc2JEMGljbWRpWVNneU5UVXNNalUxTERJMU5Td3dMallwSWo1SlJEb2dQQzkwYzNCaGJqNHhQQzkwWlhoMFBqd3ZaejRnUEdjZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVb01qbHdlQ3dnTkRFMGNIZ3BJajQ4Y21WamRDQjNhV1IwYUQwaU1UQTFjSGdpSUdobGFXZG9kRDBpTWpad2VDSWdjbmc5SWpod2VDSWdjbms5SWpod2VDSWdabWxzYkQwaWNtZGlZU2d3TERBc01Dd3dMallwSWlBdlBqeDBaWGgwSUhnOUlqRXljSGdpSUhrOUlqRTNjSGdpSUdadmJuUXRabUZ0YVd4NVBTSW5RMjkxY21sbGNpQk9aWGNuTENCdGIyNXZjM0JoWTJVaUlHWnZiblF0YzJsNlpUMGlNVEp3ZUNJZ1ptbHNiRDBpZDJocGRHVWlQangwYzNCaGJpQm1hV3hzUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERBdU5pa2lQazFwYmlCVWFXTnJPaUE4TDNSemNHRnVQakE4TDNSbGVIUStQQzluUGlBOFp5QnpkSGxzWlQwaWRISmhibk5tYjNKdE9uUnlZVzV6YkdGMFpTZ3lPWEI0TENBME5EUndlQ2tpUGp4eVpXTjBJSGRwWkhSb1BTSXhNalp3ZUNJZ2FHVnBaMmgwUFNJeU5uQjRJaUJ5ZUQwaU9IQjRJaUJ5ZVQwaU9IQjRJaUJtYVd4c1BTSnlaMkpoS0RBc01Dd3dMREF1TmlraUlDOCtQSFJsZUhRZ2VEMGlNVEp3ZUNJZ2VUMGlNVGR3ZUNJZ1ptOXVkQzFtWVcxcGJIazlJaWREYjNWeWFXVnlJRTVsZHljc0lHMXZibTl6Y0dGalpTSWdabTl1ZEMxemFYcGxQU0l4TW5CNElpQm1hV3hzUFNKM2FHbDBaU0krUEhSemNHRnVJR1pwYkd3OUluSm5ZbUVvTWpVMUxESTFOU3d5TlRVc01DNDJLU0krVFdGNElGUnBZMnM2SUR3dmRITndZVzQrTVRBd01Ed3ZkR1Y0ZEQ0OEwyYytQR2NnYzNSNWJHVTlJblJ5WVc1elptOXliVHAwY21GdWMyeGhkR1VvTWpJMmNIZ3NJRFF6TTNCNEtTSStQSEpsWTNRZ2QybGtkR2c5SWpNMmNIZ2lJR2hsYVdkb2REMGlNelp3ZUNJZ2NuZzlJamh3ZUNJZ2NuazlJamh3ZUNJZ1ptbHNiRDBpYm05dVpTSWdjM1J5YjJ0bFBTSnlaMkpoS0RJMU5Td3lOVFVzTWpVMUxEQXVNaWtpSUM4K1BIQmhkR2dnYzNSeWIydGxMV3hwYm1WallYQTlJbkp2ZFc1a0lpQmtQU0pOT0NBNVF6Z3VNREF3TURRZ01qSXVPVFE1TkNBeE5pNHlNRGs1SURJNElESTNJREk0SWlCbWFXeHNQU0p1YjI1bElpQnpkSEp2YTJVOUluZG9hWFJsSWlBdlBqeGphWEpqYkdVZ2MzUjViR1U5SW5SeVlXNXpabTl5YlRwMGNtRnVjMnhoZEdVelpDZ3hNM0I0TENBeU0zQjRMQ0F3Y0hncElpQmplRDBpTUhCNElpQmplVDBpTUhCNElpQnlQU0kwY0hnaUlHWnBiR3c5SW5kb2FYUmxJaTgrUEM5blBqeG5JSE4wZVd4bFBTSjBjbUZ1YzJadmNtMDZkSEpoYm5Oc1lYUmxLREl5Tm5CNExDQXpPVEp3ZUNraVBqeHlaV04wSUhkcFpIUm9QU0l6Tm5CNElpQm9aV2xuYUhROUlqTTJjSGdpSUhKNFBTSTRjSGdpSUhKNVBTSTRjSGdpSUdacGJHdzlJbTV2Ym1VaUlITjBjbTlyWlQwaWNtZGlZU2d5TlRVc01qVTFMREkxTlN3d0xqSXBJaUF2UGp4blBqeHdZWFJvSUhOMGVXeGxQU0owY21GdWMyWnZjbTA2ZEhKaGJuTnNZWFJsS0Rad2VDdzJjSGdwSWlCa1BTSk5NVElnTUV3eE1pNDJOVEl5SURrdU5UWTFPRGRNTVRnZ01TNDJNRGMzVERFekxqYzRNVGtnTVRBdU1qRTRNVXd5TWk0ek9USXpJRFpNTVRRdU5ETTBNU0F4TVM0ek5EYzRUREkwSURFeVRERTBMalF6TkRFZ01USXVOalV5TWt3eU1pNHpPVEl6SURFNFRERXpMamM0TVRrZ01UTXVOemd4T1V3eE9DQXlNaTR6T1RJelRERXlMalkxTWpJZ01UUXVORE0wTVV3eE1pQXlORXd4TVM0ek5EYzRJREUwTGpRek5ERk1OaUF5TWk0ek9USXpUREV3TGpJeE9ERWdNVE11TnpneE9Vd3hMall3TnpjZ01UaE1PUzQxTmpVNE55QXhNaTQyTlRJeVREQWdNVEpNT1M0MU5qVTROeUF4TVM0ek5EYzRUREV1TmpBM055QTJUREV3TGpJeE9ERWdNVEF1TWpFNE1VdzJJREV1TmpBM04wd3hNUzR6TkRjNElEa3VOVFkxT0RkTU1USWdNRm9pSUdacGJHdzlJbmRvYVhSbElpQXZQanhoYm1sdFlYUmxWSEpoYm5ObWIzSnRJR0YwZEhKcFluVjBaVTVoYldVOUluUnlZVzV6Wm05eWJTSWdkSGx3WlQwaWNtOTBZWFJsSWlCbWNtOXRQU0l3SURFNElERTRJaUIwYnowaU16WXdJREU0SURFNElpQmtkWEk5SWpFd2N5SWdjbVZ3WldGMFEyOTFiblE5SW1sdVpHVm1hVzVwZEdVaUx6NDhMMmMrUEM5blBqd3ZjM1puUGc9PSJ9"`; + +exports[`NFTDescriptor #svgImage matches the current snapshot 1`] = `" 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0x1234567890123456789123456789012345678901 • UNI 0x1234567890123456789123456789012345678901 • UNI UNI/WNativeToken ID: 123 Min Tick: -1000 Max Tick: 2000"`; diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.svg b/src/periphery/test/__snapshots__/NFTDescriptor.svg new file mode 100644 index 000000000..281d91c0b --- /dev/null +++ b/src/periphery/test/__snapshots__/NFTDescriptor.svg @@ -0,0 +1 @@ + 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf • WNativeToken 0x1234567890123456789123456789012345678901 • UNI 0x1234567890123456789123456789012345678901 • UNI UNI/WNativeToken ID: 123 Min Tick: -1000 Max Tick: 2000 \ No newline at end of file diff --git a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index 8cdeb2572..b8dbc5933 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -2,29 +2,29 @@ exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `61849`; -exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `121048`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `120619`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `117386`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `116957`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `117577`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `117148`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4549075`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4761816`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171152`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `167685`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `175948`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `171771`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `178884`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `177588`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `629574`; +exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `628151`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `642944`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `641524`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `635777`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `634357`; -exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `437499`; +exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `436164`; -exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `327270`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `325974`; exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `59310`; @@ -36,4 +36,4 @@ exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @ski exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `24267`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `244883`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `241370`; diff --git a/src/periphery/test/__snapshots__/Path.spec.ts.snap b/src/periphery/test/__snapshots__/Path.spec.ts.snap new file mode 100644 index 000000000..12dec0f48 --- /dev/null +++ b/src/periphery/test/__snapshots__/Path.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Path gas cost [ @skip-on-coverage ] 1`] = `254`; diff --git a/src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap b/src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap new file mode 100644 index 000000000..a25564600 --- /dev/null +++ b/src/periphery/test/__snapshots__/PeripheryImmutableState.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PeripheryImmutableState bytecode size [ @skip-on-coverage ] 1`] = `233`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap new file mode 100644 index 000000000..87654f4bf --- /dev/null +++ b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PoolAddress #computeAddress gas cost [ @skip-on-coverage ] 1`] = `625`; + +exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0x7af6fBa41756B3B52A79ecd40dA16d4711910116"`; diff --git a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap new file mode 100644 index 000000000..12dcf6860 --- /dev/null +++ b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PositionValue #fees when price is above the position range gas 1`] = `48350`; + +exports[`PositionValue #fees when price is below the position range gas 1`] = `48416`; + +exports[`PositionValue #fees when price is within the position range gas 1`] = `53919`; + +exports[`PositionValue #principal gas 1`] = `23152`; + +exports[`PositionValue #total gas 1`] = `57010`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap new file mode 100644 index 000000000..f1b0678f4 --- /dev/null +++ b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `156384`; + +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156332`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `247726`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `105983`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `90651`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `126079`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `156312`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `126088`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `126410`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `156917`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `156921`; + +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91433`; + +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `91632`; + +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `91649`; diff --git a/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap b/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap new file mode 100644 index 000000000..249d0d811 --- /dev/null +++ b/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SwapRouter bytecode size [ @skip-on-coverage ] 1`] = `12378`; diff --git a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap new file mode 100644 index 000000000..c079971b5 --- /dev/null +++ b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TickLens #getClosestActiveTicks gas for almost all tick space [ @skip-on-coverage ] 1`] = `31485`; + +exports[`TickLens #getNextActiveTicks gas for single populated tick [ @skip-on-coverage ] 1`] = `22142`; + +exports[`TickLens #getPopulatedTicksInWord gas for single populated tick [ @skip-on-coverage ] 1`] = `53690`; + +exports[`TickLens fully populated word getNextActiveTicks 255 ticks [ @skip-on-coverage ] 1`] = `2460078`; + +exports[`TickLens fully populated word getNextActiveTicks 512 ticks [ @skip-on-coverage ] 1`] = `2501382`; + +exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `92513`; diff --git a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap new file mode 100644 index 000000000..1e286f4bb --- /dev/null +++ b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`V3Migrator #migrate gas [ @skip-on-coverage ] 1`] = `747042`; diff --git a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 795dc80d1..44eb416d7 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`] = `208167`; -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`] = `160182`; -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`] = `195620`; -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`] = `188406`; -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`] = `156674`; -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`] = `205281`; -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`] = `155028`; -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`] = `188493`; -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`] = `199427`; -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`] = `154500`; -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`] = `115354`; -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`] = `108718`; -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`] = `113518`; -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`] = `125233`; -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`] = `113126`; -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`] = `117926`; -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`] = `124790`; -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`] = `109379`; -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`] = `114179`; -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`] = `126330`; -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`] = `280193`; -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`] = `143430`; -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`] = `151399`; -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`] = `358307`; -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`] = `168499`; -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`] = `126898`; -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`] = `354538`; -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`] = `143998`; -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`] = `61572`; -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`] = `149595`; -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`] = `149564`; -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`] = `150288`; -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`] = `166155`; -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`] = `199705`; -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`] = `149662`; -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`] = `199705`; -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`] = `218905`; -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`] = `114062`; -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`] = `114026`; -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`] = `129807`; -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`] = `164178`; -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`] = `226667`; -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`] = `157195`; -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`] = `149656`; -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`] = `149604`; -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`] = `114087`; -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`] = `180872`; -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`] = `180820`; -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`] = `128203`; -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`] = `160047`; -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`] = `160016`; -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`] = `150525`; -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`] = `176844`; -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`] = `211105`; -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`] = `160114`; -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`] = `211105`; -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`] = `230305`; -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`] = `124514`; -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`] = `124478`; -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`] = `140496`; -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`] = `175578`; -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`] = `238067`; -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`] = `157432`; -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`] = `160119`; -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`] = `160067`; -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`] = `124550`; -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`] = `191335`; -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`] = `191283`; -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`] = `138666`; From 2e1875974306682d225c5438111b88ddb4f44134 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 17 Jul 2024 14:10:44 +0300 Subject: [PATCH 10/60] [Core] add handle plugin fee call --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/base/ReservesManager.sol | 37 ++++++++++++++----- .../interfaces/plugin/IAlgebraPlugin.sol | 6 +++ src/core/contracts/test/MockPoolPlugin.sol | 4 ++ .../contracts/libraries/PoolAddress.sol | 2 +- 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 4cba6201a..c7c3ec8e3 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x0ac47702697289e1a4e48eb16dba55f0d3da9ca5fa34e946cd034ec13de42e49; + bytes32 public constant POOL_INIT_CODE_HASH = 0xd8f09a288fbf8326edf3ce1f854c1cf39cf4cd8c4da1ae002dc1538e677e386a; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index fea298901..43d268b3a 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -3,6 +3,7 @@ pragma solidity =0.8.20; import '../libraries/SafeCast.sol'; import './AlgebraPoolBase.sol'; +import '../interfaces/plugin/IAlgebraPlugin.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 @@ -73,7 +74,7 @@ abstract contract ReservesManager is AlgebraPoolBase { address feesRecipient, bytes32 slot, uint32 lastTimestamp - ) internal returns (int256, int256) { + ) internal returns (int256, int256, uint256, uint256) { uint256 feePending0; uint256 feePending1; @@ -90,19 +91,30 @@ abstract contract ReservesManager is AlgebraPoolBase { feePending0 += fee0; feePending1 += fee1; - uint32 currentTimestamp = _blockTimestamp(); - if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY || feePending0 > type(uint104).max || feePending1 > type(uint104).max) { - if (feePending0 > 0) _transfer(token0, feesRecipient, feePending0); - if (feePending1 > 0) _transfer(token1, feesRecipient, feePending1); - (deltaR0, deltaR1) = (deltaR0 - feePending0.toInt256(), deltaR1 - feePending1.toInt256()); - (feePending0, feePending1) = (0, 0); + + uint256 feeSent0; + uint256 feeSent1; + + if (_blockTimestamp() - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY || feePending0 > type(uint104).max || feePending1 > type(uint104).max) { + if (feePending0 > 0) { + _transfer(token0, feesRecipient, feePending0); + deltaR0 = deltaR0 - feePending0.toInt256(); + feeSent0 = feePending0; + feePending0 = 0; + } + if (feePending1 > 0) { + _transfer(token1, feesRecipient, feePending1); + deltaR1 = deltaR1 - feePending1.toInt256(); + feeSent1 = feePending1; + feePending1 = 0; + } } assembly { sstore(slot, or(or(feePending0, shl(104, feePending1)), shl(208, lastTimestamp))) } - return (deltaR0, deltaR1); + return (deltaR0, deltaR1, feeSent0, feeSent1); } /// @notice Applies deltas to reserves and pays communityFees @@ -127,14 +139,19 @@ abstract contract ReservesManager is AlgebraPoolBase { assembly { slot := communityFeePending0.slot } - (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, communityFee0, communityFee1, communityVault, slot, lastTimestamp); + (deltaR0, deltaR1, , ) = updateFeeAmounts(deltaR0, deltaR1, communityFee0, communityFee1, communityVault, slot, lastTimestamp); } if (pluginFee0 | pluginFee1 != 0) { assembly { slot := pluginFeePending0.slot } - (deltaR0, deltaR1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); + uint256 pluginFeeSent0; + uint256 pluginFeeSent1; + (deltaR0, deltaR1, pluginFeeSent0, pluginFeeSent1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); + if (pluginFeeSent0 > 0 || pluginFeeSent1 > 0) { + IAlgebraPlugin(plugin).handlePluginFee(pluginFeeSent0, pluginFeeSent1); + } } if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) lastFeeTransferTimestamp = currentTimestamp; diff --git a/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol b/src/core/contracts/interfaces/plugin/IAlgebraPlugin.sol index c009389b5..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 diff --git a/src/core/contracts/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index f67d7c8a8..fe79db9d7 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -60,6 +60,10 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { selectorsDisableConfig = newSelectorsDisableConfig; } + function handlePluginFee(uint256, uint256) external pure override returns (bytes4) { + return IAlgebraPlugin.handlePluginFee.selector; + } + /// @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 diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index f75fa003b..0cb726334 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 = 0x0ac47702697289e1a4e48eb16dba55f0d3da9ca5fa34e946cd034ec13de42e49; + bytes32 internal constant POOL_INIT_CODE_HASH = 0xd8f09a288fbf8326edf3ce1f854c1cf39cf4cd8c4da1ae002dc1538e677e386a; /// @notice The identifying key of the pool struct PoolKey { From 0a2f7b62395f2d6d1c9f0bc2114cc4f1bcfd4cf5 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 17 Jul 2024 14:44:07 +0300 Subject: [PATCH 11/60] [Plugin] add handlePluginFee and collectPluginFee to base plugin --- src/plugin/contracts/AlgebraBasePluginV1.sol | 12 ++++++++++++ .../contracts/interfaces/IAlgebraBasePluginV1.sol | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 53fdcf616..8dde7e8b1 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -3,6 +3,7 @@ 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/plugin/IAlgebraPlugin.sol'; @@ -107,6 +108,17 @@ contract AlgebraBasePluginV1 is IAlgebraBasePluginV1, Timestamp, IAlgebraPlugin _updatePluginConfigInPool(); } + /// @inheritdoc IAlgebraBasePluginV1 + 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; + } + // ###### Volatility and TWAP oracle ###### /// @inheritdoc IVolatilityOracle diff --git a/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol b/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol index 7567769e9..84efe37a5 100644 --- a/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol +++ b/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol @@ -13,4 +13,10 @@ interface IAlgebraBasePluginV1 is IVolatilityOracle, IDynamicFeeManager, IFarmin /// @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 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; } From 9b857576932c2bb4765f6e88d21779294cb75e19 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 17 Jul 2024 15:20:37 +0300 Subject: [PATCH 12/60] [Core] add selector check to reservesManager --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/base/ReservesManager.sol | 3 ++- src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol | 6 +++++- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index c7c3ec8e3..ee08490f6 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0xd8f09a288fbf8326edf3ce1f854c1cf39cf4cd8c4da1ae002dc1538e677e386a; + bytes32 public constant POOL_INIT_CODE_HASH = 0x56e276f77642a0be95d68ca21438b260c1472a2edc27b59653a9c8cc7c347ed1; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index 43d268b3a..488e24ef9 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -4,6 +4,7 @@ pragma solidity =0.8.20; import '../libraries/SafeCast.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 @@ -150,7 +151,7 @@ abstract contract ReservesManager is AlgebraPoolBase { uint256 pluginFeeSent1; (deltaR0, deltaR1, pluginFeeSent0, pluginFeeSent1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); if (pluginFeeSent0 > 0 || pluginFeeSent1 > 0) { - IAlgebraPlugin(plugin).handlePluginFee(pluginFeeSent0, pluginFeeSent1); + if (IAlgebraPlugin(plugin).handlePluginFee(pluginFeeSent0, pluginFeeSent1) != IAlgebraPlugin.handlePluginFee.selector) revert IAlgebraPoolErrors.invalidPluginResponce(); } } diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol index 5ffb6917f..b97a81122 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol @@ -24,9 +24,13 @@ 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 error incorrectPluginFee(); + /// @notice Emitted if a plugin returns invalid selector after pluginFeeHandle call + error invalidPluginResponce(); + /// @notice Emitted if the pool received fewer tokens than it should have error insufficientInputAmount(); diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 0cb726334..10bb5661f 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 = 0xd8f09a288fbf8326edf3ce1f854c1cf39cf4cd8c4da1ae002dc1538e677e386a; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x56e276f77642a0be95d68ca21438b260c1472a2edc27b59653a9c8cc7c347ed1; /// @notice The identifying key of the pool struct PoolKey { From d7af9a4621f79cb77f16890ef230faf6cf8fab51 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 17 Jul 2024 16:01:45 +0300 Subject: [PATCH 13/60] [Common] update snapshots --- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 ++--- .../AlgebraPool.gas.spec.ts.snap | 22 +++++----- .../__snapshots__/NFTDescriptor.spec.ts.snap | 2 +- .../NonfungiblePositionManager.spec.ts.snap | 2 +- .../__snapshots__/PoolAddress.spec.ts.snap | 2 +- .../AlgebraBasePluginV1.spec.ts.snap | 2 +- .../AlgebraPool.gas.spec.ts.snap | 42 +++++++++---------- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 1ab463f20..1e2b36314 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`] = `4782479`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4833685`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4782479`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4833685`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4769727`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4820932`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4769727`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4820932`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22589`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22844`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 72001a6d4..727731946 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -146,35 +146,35 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61596`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113808`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113855`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103562`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129790`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129837`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164015`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164062`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113946`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113993`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164015`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164062`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183215`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183262`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113808`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113855`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103557`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130614`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130661`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164866`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164913`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183215`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183262`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103431`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103427`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113880`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113927`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103602`; diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap index 46f66ba69..84b6fb99c 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`] = `1616570`; +exports[`NFTDescriptor #constructTokenURI gas [ @skip-on-coverage ] 1`] = `1616588`; 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 b8dbc5933..e3a2aaa68 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -8,7 +8,7 @@ exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-o exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `117148`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4761816`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4813021`; exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `167685`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap index 87654f4bf..8fd151b21 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`] = `625`; -exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0x7af6fBa41756B3B52A79ecd40dA16d4711910116"`; +exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0x6C7bF9CdFBA917dAd9Ba260C0a2Abab05F44200e"`; diff --git a/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap index 90337d498..3a2ab609a 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`] = `23779`; diff --git a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 44eb416d7..1aa9042af 100644 --- a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -96,48 +96,48 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `114087`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `180872`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `180850`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `180820`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `180798`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128203`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128181`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160047`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160094`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160016`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160063`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150525`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `176844`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `176891`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211105`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211152`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160114`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160161`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211105`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211152`; -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`] = `230305`; +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`] = `230352`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124514`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124561`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124478`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124525`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140496`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140543`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175578`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175625`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238067`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238114`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157432`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160119`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160166`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160067`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160114`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124550`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124597`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191335`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191360`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191283`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138666`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138691`; From 17408d7be5733385bafff724de00aefb6ff638de Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 18 Jul 2024 13:47:34 +0300 Subject: [PATCH 14/60] [Periphery] fix tests after merge & update snapshots --- src/periphery/contracts/test/CustomPlugin.sol | 12 +++++--- src/periphery/contracts/test/MockPlugin.sol | 12 +++++--- .../__snapshots__/NFTDescriptor.spec.ts.snap | 2 +- .../NonfungiblePositionManager.spec.ts.snap | 26 ++++++++--------- .../__snapshots__/PositionValue.spec.ts.snap | 4 +-- .../test/__snapshots__/QuoterV2.spec.ts.snap | 28 +++++++++---------- .../__snapshots__/V3Migrator.spec.ts.snap | 2 +- 7 files changed, 47 insertions(+), 39 deletions(-) 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/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..63145f893 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -2,29 +2,29 @@ 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`] = `123818`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120002`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120156`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120193`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120347`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5263839`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5313986`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169445`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169544`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174001`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174095`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `180336`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `180521`; -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`] = `633006`; -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`] = `647369`; -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`] = `640202`; -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`] = `438706`; -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`] = `328516`; exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60014`; @@ -36,4 +36,4 @@ exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @ski exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `21884`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245031`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245216`; diff --git a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap index a2107a0d0..9839aa3b7 100644 --- a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap +++ b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap @@ -4,8 +4,8 @@ exports[`PositionValue #fees when price is above the position range gas 1`] = `5 exports[`PositionValue #fees when price is below the position range gas 1`] = `51867`; -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`] = `57370`; exports[`PositionValue #principal gas 1`] = `26316`; -exports[`PositionValue #total gas 1`] = `60417`; +exports[`PositionValue #total gas 1`] = `60461`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap index 5fa868d57..280652ce0 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`] = `156810`; -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156109`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156758`; -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`] = `248651`; -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`] = `106394`; -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`] = `91062`; -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`] = `126490`; -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`] = `156723`; -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`] = `126499`; -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`] = `126821`; -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`] = `157328`; -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`] = `157332`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91417`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91947`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `91513`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92043`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `91530`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92060`; diff --git a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap index 00144d0d4..01bffcc67 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`] = `752120`; From 50eb2ab249d415ed768ea4fb0e77f94e45c2f1f4 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 02:57:49 +0300 Subject: [PATCH 15/60] [Core] add max fee check --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 2 ++ src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol | 7 +++++-- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index ee08490f6..2f2f8b719 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x56e276f77642a0be95d68ca21438b260c1472a2edc27b59653a9c8cc7c347ed1; + bytes32 public constant POOL_INIT_CODE_HASH = 0x076a5ddd653cb1327a88ab5223ec2db3e2b18d82f66ad440701f4f5429bc8b23; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index c82a55bb3..7b41d1b56 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -134,6 +134,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio int128 liquidityDelta = -int128(amount); uint24 pluginFee = _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); + if (pluginFee > 1e6) revert incorrectOverrideFee(); _lock(); _updateReserves(); @@ -238,6 +239,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio bytes calldata data ) external override returns (int256 amount0, int256 amount1) { (uint24 overrideFee, uint24 pluginFee) = _beforeSwap(recipient, zeroToOne, amountRequired, limitSqrtPrice, false, data); + if (overrideFee > 1e6) revert incorrectOverrideFee(); _lock(); { diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol index b97a81122..fbc1ebb66 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol @@ -24,13 +24,16 @@ 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 + + /// @notice Emitted if plugin fee param greater than fee/override fee error incorrectPluginFee(); /// @notice Emitted if a plugin returns invalid selector after pluginFeeHandle call error invalidPluginResponce(); + /// @notice Emitted if override fee param greater than 1e6 + error incorrectOverrideFee(); + /// @notice Emitted if the pool received fewer tokens than it should have error insufficientInputAmount(); diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 387afa58b..59de6d8f2 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 = 0x56e276f77642a0be95d68ca21438b260c1472a2edc27b59653a9c8cc7c347ed1; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x076a5ddd653cb1327a88ab5223ec2db3e2b18d82f66ad440701f4f5429bc8b23; /// @notice The identifying key of the pool struct PoolKey { From cc92cd328bea2f3ddfaebfc4a529516e44ef8637 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 03:44:45 +0300 Subject: [PATCH 16/60] [Common] reduce core runs && update snapshots --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/hardhat.config.ts | 2 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 120 +++++++++--------- .../contracts/libraries/PoolAddress.sol | 2 +- .../NonfungiblePositionManager.spec.ts.snap | 14 +- .../__snapshots__/PoolAddress.spec.ts.snap | 2 +- .../test/__snapshots__/QuoterV2.spec.ts.snap | 28 ++-- .../AlgebraPool.gas.spec.ts.snap | 120 +++++++++--------- 9 files changed, 150 insertions(+), 150 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 2f2f8b719..ff2d2fe09 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x076a5ddd653cb1327a88ab5223ec2db3e2b18d82f66ad440701f4f5429bc8b23; + bytes32 public constant POOL_INIT_CODE_HASH = 0x27de542bde58f0e4ab6e8bd0ce50ec26a6ab91af889922ed6638bf2c4795b696; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/hardhat.config.ts b/src/core/hardhat.config.ts index bf22e9b63..415c566da 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: 800, + runs: 600, }, metadata: { bytecodeHash: 'none', diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 1e2b36314..396f4b11d 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`] = `4842335`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4833685`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4842335`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4820932`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4829582`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4820932`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4829582`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10609`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22844`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22887`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 727731946..fc77c8325 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,29 +4,29 @@ 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`] = `115373`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `115399`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115373`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115399`; -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`] = `108742`; +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`] = `108774`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113542`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113574`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125252`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125278`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125252`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125278`; -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`] = `113150`; +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`] = `113182`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `117950`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `117982`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124809`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124835`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124809`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124835`; -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`] = `109403`; +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`] = `109435`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114203`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114235`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #collect close to worst case 1`] = `52641`; @@ -56,65 +56,65 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below curr exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below current price second position in same range 1`] = `144106`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61596`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #poke best case 1`] = `61628`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103356`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103388`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103325`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103357`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119101`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119133`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152615`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152647`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103494`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103526`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152615`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152647`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171815`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171847`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103356`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103388`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103320`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103352`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119925`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119957`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153466`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153498`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171815`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171847`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103194`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103226`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103190`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103222`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103417`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103449`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103365`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103397`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103381`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103413`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115373`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115399`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115373`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115399`; -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`] = `108742`; +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`] = `108774`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113542`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113574`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125252`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125278`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125252`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125278`; -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`] = `113150`; +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`] = `113182`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `117950`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `117982`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124809`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124835`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124809`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124835`; -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`] = `109403`; +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`] = `109435`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114203`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114235`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #collect close to worst case 1`] = `52641`; @@ -144,38 +144,38 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below current price second position in same range 1`] = `144106`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61596`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #poke best case 1`] = `61628`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113855`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113887`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103562`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103594`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129837`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129869`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164062`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164094`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113993`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `114025`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164062`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164094`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183262`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183294`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113855`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113887`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103557`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103589`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130661`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130693`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164913`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164945`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183262`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183294`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103431`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103463`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103427`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103459`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113927`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113959`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103602`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103634`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103618`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103650`; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 59de6d8f2..3d7995475 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 = 0x076a5ddd653cb1327a88ab5223ec2db3e2b18d82f66ad440701f4f5429bc8b23; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x27de542bde58f0e4ab6e8bd0ce50ec26a6ab91af889922ed6638bf2c4795b696; /// @notice The identifying key of the pool struct PoolKey { diff --git a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index 63145f893..3991a1b93 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -2,17 +2,17 @@ exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `62302`; -exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `123818`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `123850`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120156`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120188`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120347`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120379`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5313986`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5322624`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169544`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169570`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174095`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174127`; exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `180521`; @@ -36,4 +36,4 @@ exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @ski exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `21884`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245216`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245242`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap index b873a70d7..803c37341 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`] = `"0x6C7bF9CdFBA917dAd9Ba260C0a2Abab05F44200e"`; +exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0xdbb5FB6c3D4513cD9a833a881cf37219C3cF8339"`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap index 280652ce0..22debf23c 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`] = `156810`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `156854`; -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156758`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156802`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `248651`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `248739`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `106394`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `106438`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `91062`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `91106`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `126490`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `126534`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `156723`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `156767`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `126499`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `126543`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `126821`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `126865`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `157328`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `157372`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `157332`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `157376`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91947`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91991`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92043`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92087`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92060`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92104`; diff --git a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 1aa9042af..e7461f685 100644 --- a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -1,42 +1,42 @@ // 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`] = `208167`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee large swap crossing several initialized ticks 1`] = `208199`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `160182`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `160214`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `195620`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `195652`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `188406`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `188438`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `156674`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `156706`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `205281`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `205313`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `155028`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `155060`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `188493`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `188525`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `199427`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `199459`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `154500`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `154532`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `115354`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `115380`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `108718`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `108750`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `113518`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `113550`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `125233`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `125259`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `113126`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `113158`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `117926`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `117958`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `124790`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `124816`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `109379`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `109411`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `114179`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `114211`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #collect close to worst case 1`] = `52593`; @@ -60,84 +60,84 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below curre exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below current price second position in same range 1`] = `143998`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #poke best case 1`] = `61572`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #poke best case 1`] = `61604`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `149595`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `149627`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `149564`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `149596`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150288`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150320`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `166155`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `166187`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `199705`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `199737`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `149662`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `149694`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `199705`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `199737`; -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`] = `218905`; +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`] = `218937`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114062`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114094`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `114026`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `114058`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129807`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129839`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164178`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164210`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `226667`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `226699`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157195`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157227`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `149656`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `149688`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `149604`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `149636`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `114087`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `114119`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `180850`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `180882`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `180798`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `180830`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128181`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128213`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160094`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160126`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160063`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160095`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150525`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150557`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `176891`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `176923`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211152`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211184`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160161`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160193`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211152`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211184`; -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`] = `230352`; +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`] = `230384`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124561`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124593`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124525`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124557`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140543`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140575`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175625`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175657`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238114`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238146`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157432`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157464`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160166`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160198`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160114`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160146`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124597`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124629`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191360`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191392`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191308`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191340`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138691`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138723`; From 19ad28ea1a65ef83c02b967cca97c053537f6df1 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 12:19:24 +0300 Subject: [PATCH 17/60] [Core] add plugin fee pending getter --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/base/AlgebraPoolBase.sol | 4 ++++ src/core/contracts/interfaces/pool/IAlgebraPoolState.sol | 6 ++++++ src/core/hardhat.config.ts | 2 +- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index ff2d2fe09..f6312e6eb 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x27de542bde58f0e4ab6e8bd0ce50ec26a6ab91af889922ed6638bf2c4795b696; + bytes32 public constant POOL_INIT_CODE_HASH = 0x36e775f5fbc9f609c551e523f028f19fdafde4c7a92470b6319566f8c8a6ba40; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/base/AlgebraPoolBase.sol b/src/core/contracts/base/AlgebraPoolBase.sol index 530433d31..1fc657ca4 100644 --- a/src/core/contracts/base/AlgebraPoolBase.sol +++ b/src/core/contracts/base/AlgebraPoolBase.sol @@ -137,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/interfaces/pool/IAlgebraPoolState.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol index 6606a3f37..8d6803479 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolState.sol @@ -78,6 +78,12 @@ interface IAlgebraPoolState { /// @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/hardhat.config.ts b/src/core/hardhat.config.ts index 415c566da..b130f2216 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: 600, + runs: 500, }, metadata: { bytecodeHash: 'none', diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 3d7995475..3db69c42e 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 = 0x27de542bde58f0e4ab6e8bd0ce50ec26a6ab91af889922ed6638bf2c4795b696; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x36e775f5fbc9f609c551e523f028f19fdafde4c7a92470b6319566f8c8a6ba40; /// @notice The identifying key of the pool struct PoolKey { From c8aa329cbd0c993a5d3f133416363a17b6872266 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 12:29:05 +0300 Subject: [PATCH 18/60] [Core] add plugin fee tests --- src/core/contracts/test/MockPoolPlugin.sol | 23 ++++-- src/core/test/AlgebraPool.spec.ts | 90 ++++++++++++++++++++++ src/plugin/contracts/test/MockPool.sol | 5 ++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/core/contracts/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index fe79db9d7..27ebf15db 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -10,6 +10,9 @@ import '../libraries/Plugins.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,10 +63,19 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { selectorsDisableConfig = newSelectorsDisableConfig; } - function handlePluginFee(uint256, uint256) external pure override returns (bytes4) { + 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 @@ -97,8 +109,9 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { bytes calldata data ) 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, 0); - return (IAlgebraPlugin.defaultPluginConfig.selector, 0); + if (!Plugins.hasFlag(selectorsDisableConfig, Plugins.BEFORE_POSITION_MODIFY_FLAG)) + return (IAlgebraPlugin.beforeModifyPosition.selector, overrideFee); + return (IAlgebraPlugin.defaultPluginConfig.selector, overrideFee); } /// @notice The hook called after a position is modified @@ -132,8 +145,8 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { bytes calldata data ) 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, 0, 0); - return (IAlgebraPlugin.defaultPluginConfig.selector, 0, 0); + 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 diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 59d9dff2c..2039f2631 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2430,6 +2430,96 @@ 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 fails if plugin fee greater than override fee', async () => { + await poolPlugin.setPluginFees(3000, 4000); + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'incorrectPluginFee'); + }) + + it('swap fails if plugin fee exceeds max value', async () => { + await poolPlugin.setPluginFees(1000001, 4000); + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'incorrectOverrideFee'); + await expect(pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x')).to.be.revertedWithCustomError(pool, 'incorrectOverrideFee'); + }) + + it('swap fails if plugin return incorrect selector', async () => { + await poolPlugin.disablePluginFeeHandle(); + await poolPlugin.setPluginFees(5000, 4000); + await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'invalidPluginResponce') ; + }) + + 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%, 100%', async () => { + await poolPlugin.setPluginFees(500000, 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(750000, 750000); + await swapExact1For0(expandTo18Decimals(1), wallet.address); + pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 125n / 100n); + + await poolPlugin.setPluginFees(1000000, 1000000); + await swapExact1For0(expandTo18Decimals(1), wallet.address); + pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 225n / 100n); + }) + + it('works correct on burn', async () => { + await poolPlugin.setPluginFees(6000, 0); + await pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x') + let pluginFees = await pool.getPluginFeePending(); + expect(pluginFees[0]).to.be.eq(6n * 10n**15n-1n); + expect(pluginFees[1]).to.be.eq(6n * 10n**15n-1n) + }) + + 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('works correct with communityFee', async () => { + await poolPlugin.setPluginFees(5000, 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); + }) + + }) + describe('PermissionedActions', async () => { describe('#setCommunityFee', () => { beforeEach('initialize the pool', async () => { diff --git a/src/plugin/contracts/test/MockPool.sol b/src/plugin/contracts/test/MockPool.sol index e191229ee..1c2604c58 100644 --- a/src/plugin/contracts/test/MockPool.sol +++ b/src/plugin/contracts/test/MockPool.sol @@ -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'); From 755774fee2b59e2fe7f27767cd2ccbdb8b1c6380 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 15:17:38 +0300 Subject: [PATCH 19/60] [Core] some fixes --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 4 ++-- src/core/contracts/base/SwapCalculation.sol | 1 - src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol | 3 --- src/core/test/AlgebraPool.spec.ts | 4 ++-- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index f6312e6eb..837aa19ee 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x36e775f5fbc9f609c551e523f028f19fdafde4c7a92470b6319566f8c8a6ba40; + bytes32 public constant POOL_INIT_CODE_HASH = 0xcdb51997e6f36c9b2b26d23cc291ed7d71f87ad7cf09ecf1a9654d8dd1b2569f; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 7b41d1b56..fadd0373c 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -134,7 +134,6 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio int128 liquidityDelta = -int128(amount); uint24 pluginFee = _beforeModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, data); - if (pluginFee > 1e6) revert incorrectOverrideFee(); _lock(); _updateReserves(); @@ -179,6 +178,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_POSITION_MODIFY_FLAG)) { bytes4 selector; (selector, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data); + if (pluginFee >= 1e6) revert incorrectPluginFee(); selector.shouldReturn(IAlgebraPlugin.beforeModifyPosition.selector); } } @@ -239,7 +239,6 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio bytes calldata data ) external override returns (int256 amount0, int256 amount1) { (uint24 overrideFee, uint24 pluginFee) = _beforeSwap(recipient, zeroToOne, amountRequired, limitSqrtPrice, false, data); - if (overrideFee > 1e6) revert incorrectOverrideFee(); _lock(); { @@ -364,6 +363,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_SWAP_FLAG)) { bytes4 selector; (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data); + if (overrideFee >= 1e6 || pluginFee > overrideFee) revert incorrectPluginFee(); selector.shouldReturn(IAlgebraPlugin.beforeSwap.selector); } } diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index 84349cfda..75fb28a62 100644 --- a/src/core/contracts/base/SwapCalculation.sol +++ b/src/core/contracts/base/SwapCalculation.sol @@ -48,7 +48,6 @@ abstract contract SwapCalculation is AlgebraPoolBase { int256 amountRequired, uint160 limitSqrtPrice ) internal returns (int256 amount0, int256 amount1, uint160 currentPrice, int24 currentTick, uint128 currentLiquidity, FeesAmount memory fees) { - if (pluginFee > overrideFee) revert incorrectPluginFee(); if (amountRequired == 0) revert zeroAmountRequired(); if (amountRequired == type(int256).min) revert invalidAmountRequired(); // to avoid problems when changing sign diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol index fbc1ebb66..d91bf4d18 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol @@ -31,9 +31,6 @@ interface IAlgebraPoolErrors { /// @notice Emitted if a plugin returns invalid selector after pluginFeeHandle call error invalidPluginResponce(); - /// @notice Emitted if override fee param greater than 1e6 - error incorrectOverrideFee(); - /// @notice Emitted if the pool received fewer tokens than it should have error insufficientInputAmount(); diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 2039f2631..954d7323d 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2449,8 +2449,8 @@ describe('AlgebraPool', () => { it('swap fails if plugin fee exceeds max value', async () => { await poolPlugin.setPluginFees(1000001, 4000); - await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'incorrectOverrideFee'); - await expect(pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x')).to.be.revertedWithCustomError(pool, 'incorrectOverrideFee'); + 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 plugin return incorrect selector', async () => { diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 3db69c42e..65a517e5e 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 = 0x36e775f5fbc9f609c551e523f028f19fdafde4c7a92470b6319566f8c8a6ba40; + bytes32 internal constant POOL_INIT_CODE_HASH = 0xcdb51997e6f36c9b2b26d23cc291ed7d71f87ad7cf09ecf1a9654d8dd1b2569f; /// @notice The identifying key of the pool struct PoolKey { From 36e4173bd7888e6727d5d57a6668bd2e6e642f26 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 18:07:12 +0300 Subject: [PATCH 20/60] [Core] add isPlugin check --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 8 ++++++++ src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 837aa19ee..e522ecda7 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0xcdb51997e6f36c9b2b26d23cc291ed7d71f87ad7cf09ecf1a9654d8dd1b2569f; + bytes32 public constant POOL_INIT_CODE_HASH = 0x53a254b73c7f4f4a23175de0908ad4b30f3bc60806bd69bba905db6f24b991a5; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index fadd0373c..1663dea28 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -168,6 +168,10 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _afterModifyPos(msg.sender, bottomTick, topTick, liquidityDelta, amount0, amount1, data); } + function _isPlugin() internal view returns (bool) { + return msg.sender == plugin; + } + function _beforeModifyPos( address owner, int24 bottomTick, @@ -176,6 +180,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio bytes calldata data ) internal returns (uint24 pluginFee) { if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_POSITION_MODIFY_FLAG)) { + if (_isPlugin()) return 0; bytes4 selector; (selector, pluginFee) = IAlgebraPlugin(plugin).beforeModifyPosition(msg.sender, owner, bottomTick, topTick, liquidityDelta, data); if (pluginFee >= 1e6) revert incorrectPluginFee(); @@ -184,6 +189,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } 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 @@ -361,6 +367,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio bytes calldata data ) internal returns (uint24 overrideFee, uint24 pluginFee) { if (globalState.pluginConfig.hasFlag(Plugins.BEFORE_SWAP_FLAG)) { + if (_isPlugin()) return (0, 0); bytes4 selector; (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data); if (overrideFee >= 1e6 || pluginFee > overrideFee) revert incorrectPluginFee(); @@ -370,6 +377,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio 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 ); diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 65a517e5e..770ef4d48 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 = 0xcdb51997e6f36c9b2b26d23cc291ed7d71f87ad7cf09ecf1a9654d8dd1b2569f; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x53a254b73c7f4f4a23175de0908ad4b30f3bc60806bd69bba905db6f24b991a5; /// @notice The identifying key of the pool struct PoolKey { From a62495edb80ee41b5f713c2f82f804c3425db972 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 19 Jul 2024 19:33:38 +0300 Subject: [PATCH 21/60] [Core] add data for plugin to createPool function --- src/core/contracts/AlgebraFactory.sol | 6 ++--- .../contracts/interfaces/IAlgebraFactory.sol | 3 ++- src/core/test/AlgebraFactory.spec.ts | 22 +++++++++---------- .../contracts/base/PoolInitializer.sol | 2 +- .../test/NonfungiblePositionManager.spec.ts | 4 ++-- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 837aa19ee..bb8839935 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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/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/test/AlgebraFactory.spec.ts b/src/core/test/AlgebraFactory.spec.ts index dda721b3f..7843aafcf 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); @@ -198,22 +198,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/periphery/contracts/base/PoolInitializer.sol b/src/periphery/contracts/base/PoolInitializer.sol index 377f4928b..0c59de7b8 100644 --- a/src/periphery/contracts/base/PoolInitializer.sol +++ b/src/periphery/contracts/base/PoolInitializer.sol @@ -32,7 +32,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, ''); _initializePool(pool, sqrtPriceX96); } diff --git a/src/periphery/test/NonfungiblePositionManager.spec.ts b/src/periphery/test/NonfungiblePositionManager.spec.ts index 31d3d0dd2..eb7fe87a7 100644 --- a/src/periphery/test/NonfungiblePositionManager.spec.ts +++ b/src/periphery/test/NonfungiblePositionManager.spec.ts @@ -102,7 +102,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 code = await wallet.provider.getCode(expectedAddress); expect(code).to.not.eq('0x'); await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(2, 1)); @@ -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)); From 3681a498219e44b1e8e6776166ac8ea2aeea289f Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 22 Jul 2024 13:42:39 +0300 Subject: [PATCH 22/60] [Common] update test snapshots & fix tests --- .../echidna/PriceMovementMathEchidnaTest.sol | 3 +- src/core/test/AlgebraPool.spec.ts | 8 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 12 +- .../AlgebraPool.gas.spec.ts.snap | 168 +++++++++--------- .../__snapshots__/EternalFarms.spec.ts.snap | 4 +- .../NonfungiblePositionManager.spec.ts.snap | 28 +-- .../__snapshots__/PoolAddress.spec.ts.snap | 2 +- .../__snapshots__/PositionValue.spec.ts.snap | 8 +- .../test/__snapshots__/QuoterV2.spec.ts.snap | 28 +-- .../test/__snapshots__/TickLens.spec.ts.snap | 12 +- .../__snapshots__/V3Migrator.spec.ts.snap | 2 +- .../AlgebraPool.gas.spec.ts.snap | 138 +++++++------- 12 files changed, 206 insertions(+), 207 deletions(-) diff --git a/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol b/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol index 2e8484c41..2d313c047 100644 --- a/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol +++ b/src/core/contracts/test/echidna/PriceMovementMathEchidnaTest.sol @@ -14,7 +14,7 @@ contract PriceMovementMathEchidnaTest { ) 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/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 954d7323d..f5bac29d9 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2448,7 +2448,7 @@ describe('AlgebraPool', () => { }) it('swap fails if plugin fee exceeds max value', async () => { - await poolPlugin.setPluginFees(1000001, 4000); + await poolPlugin.setPluginFees(1000000, 4000); 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'); }) @@ -2469,7 +2469,7 @@ describe('AlgebraPool', () => { expect(pluginFees[1]).to.be.eq(4n * 10n**15n) }) - it('works correct on swap, fee is 50%, 75%, 100%', async () => { + it('works correct on swap, fee is 50%, 75%, 99%', async () => { await poolPlugin.setPluginFees(500000, 500000); await swapExact0For1(expandTo18Decimals(1), wallet.address); await swapExact1For0(expandTo18Decimals(1), wallet.address); @@ -2481,10 +2481,10 @@ describe('AlgebraPool', () => { pluginFees = await pool.getPluginFeePending(); expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 125n / 100n); - await poolPlugin.setPluginFees(1000000, 1000000); + await poolPlugin.setPluginFees(990000, 990000); await swapExact1For0(expandTo18Decimals(1), wallet.address); pluginFees = await pool.getPluginFeePending(); - expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 225n / 100n); + expect(pluginFees[1]).to.be.eq(expandTo18Decimals(1)* 224n / 100n); }) it('works correct on burn', async () => { diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 396f4b11d..6a713244d 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`] = `4842335`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4796400`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4842335`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4796400`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4829582`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4784203`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4829582`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4784203`; -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`] = `22887`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22658`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index fc77c8325..23776de62 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,178 +4,178 @@ 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`] = `115399`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn entire position after some time passes 1`] = `117084`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `115399`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price burn when only position using ticks 1`] = `117084`; -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`] = `108774`; +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`] = `110881`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `113574`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn above current price partial position burn 1`] = `115681`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `125278`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn entire position after some time passes 1`] = `126964`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `125278`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price burn when only position using ticks 1`] = `126964`; -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`] = `113182`; +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`] = `115289`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `117982`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn around current price partial position burn 1`] = `120089`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `124835`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn entire position after some time passes 1`] = `126520`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `124835`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price burn when only position using ticks 1`] = `126520`; -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`] = `109435`; +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`] = `111542`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `114235`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #burn below current price partial position burn 1`] = `116342`; 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`] = `70408`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint above current price add to position after some time passes 1`] = `126438`; +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`] = `126438`; +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`] = `280301`; +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`] = `143538`; +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`] = `151591`; +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`] = `151591`; +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`] = `358499`; +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`] = `168691`; +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`] = `127006`; +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`] = `127006`; +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`] = `354646`; +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`] = `144106`; +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`] = `61628`; +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`] = `103388`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103318`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103357`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103287`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119133`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119063`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152647`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152577`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103526`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103456`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152647`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152577`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171847`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171777`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103388`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103318`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103352`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103282`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119957`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119887`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153498`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153428`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171847`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171777`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103226`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103156`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103222`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103152`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103449`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103379`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103397`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103327`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103413`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103343`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `115399`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117084`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `115399`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn when only position using ticks 1`] = `117084`; -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`] = `108774`; +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`] = `110881`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `113574`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price partial position burn 1`] = `115681`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `125278`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn entire position after some time passes 1`] = `126964`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `125278`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price burn when only position using ticks 1`] = `126964`; -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`] = `113182`; +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`] = `115289`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `117982`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn around current price partial position burn 1`] = `120089`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `124835`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn entire position after some time passes 1`] = `126520`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `124835`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price burn when only position using ticks 1`] = `126520`; -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`] = `109435`; +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`] = `111542`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `114235`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn below current price partial position burn 1`] = `116342`; 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 #collect close to worst case, two tokens 1`] = `70408`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint above current price add to position after some time passes 1`] = `126438`; +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 above current price add to position existing 1`] = `126438`; +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 above current price new position mint first in range 1`] = `280301`; +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 above current price second position in same range 1`] = `143538`; +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 add to position after some time passes 1`] = `151591`; +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 around current price add to position existing 1`] = `151591`; +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 around current price new position mint first in range 1`] = `358499`; +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 around current price second position in same range 1`] = `168691`; +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 add to position after some time passes 1`] = `127006`; +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 #mint below current price add to position existing 1`] = `127006`; +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 #mint below current price new position mint first in range 1`] = `354646`; +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 #mint below current price second position in same range 1`] = `144106`; +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 #poke best case 1`] = `61628`; +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 moves tick, no initialized crossings 1`] = `113887`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113817`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103594`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103524`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129869`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129799`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164094`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164024`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `114025`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113955`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164094`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164024`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183294`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183224`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113887`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113817`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103589`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103519`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130693`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130623`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164945`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164875`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183294`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183224`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103463`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103393`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103459`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103389`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113959`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113889`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103634`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103564`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103650`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103580`; diff --git a/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap b/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap index 50df3ca84..7eee69436 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`] = `498913`; -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`] = `171767`; diff --git a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index 3991a1b93..ff2e52281 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -2,29 +2,29 @@ exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `62302`; -exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `123850`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `125983`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120188`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `122321`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120379`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `122512`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5322624`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5277310`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169570`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171239`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174127`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `176207`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `180521`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `182683`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `633006`; +exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `635168`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `647369`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `649531`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `640202`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `642364`; -exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `438706`; +exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `440868`; -exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `328516`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `330678`; exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60014`; @@ -34,6 +34,6 @@ exports[`NonfungiblePositionManager #transferFrom gas [ @skip-on-coverage ] 1`] 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`] = `21901`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245242`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `246963`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap index 803c37341..52345af82 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`] = `"0xdbb5FB6c3D4513cD9a833a881cf37219C3cF8339"`; +exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0xe5Ddfe98A0958dF35aAEac697d7E589baF0c5C79"`; diff --git a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap index 9839aa3b7..3fc64e25c 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`] = `51867`; -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`] = `51933`; -exports[`PositionValue #fees when price is within the position range gas 1`] = `57370`; +exports[`PositionValue #fees when price is within the position range gas 1`] = `57458`; exports[`PositionValue #principal gas 1`] = `26316`; -exports[`PositionValue #total gas 1`] = `60461`; +exports[`PositionValue #total gas 1`] = `60549`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap index 22debf23c..77df00a47 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`] = `156854`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `156869`; -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156802`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156805`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `248739`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `248736`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `106438`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick initialized 1`] = `106435`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `91106`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 0 tick starting tick not initialized 1`] = `91088`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `126534`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 1 tick 1`] = `126531`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `156767`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 tick 1`] = `156770`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `126543`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 cross 2 where tick after is initialized 1`] = `126540`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `126865`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 1 tick 1`] = `126874`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `157372`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 ticks 1`] = `157387`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `157376`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 0 cross 2 where tick after is initialized 1`] = `157391`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91991`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91985`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92087`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92069`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92104`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92086`; diff --git a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap index c079971b5..722c60622 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`] = `31485`; +exports[`TickLens #getClosestActiveTicks gas for almost all tick space [ @skip-on-coverage ] 1`] = `31483`; -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`] = `22186`; -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`] = `53689`; -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`] = `2465710`; -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`] = `2507102`; -exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `92513`; +exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `92600`; diff --git a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap index 01bffcc67..a93877c95 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`] = `752120`; +exports[`V3Migrator #migrate gas [ @skip-on-coverage ] 1`] = `754282`; diff --git a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index e7461f685..13a477198 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`] = `208199`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee large swap crossing several initialized ticks 1`] = `208344`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `160214`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `160359`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `195652`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `195797`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `188438`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `188583`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `156706`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `156851`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `205313`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `205458`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `155060`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `155205`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `188525`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `188670`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `199459`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `199604`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `154532`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `154677`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `115380`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `117065`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `108750`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price entire position burn but other positions are using the ticks 1`] = `110857`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `113550`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price partial position burn 1`] = `115657`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `125259`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price burn when only position using ticks 1`] = `126944`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `113158`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price entire position burn but other positions are using the ticks 1`] = `115265`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `117958`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn around current price partial position burn 1`] = `120065`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `124816`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price burn when only position using ticks 1`] = `126501`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `109411`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price entire position burn but other positions are using the ticks 1`] = `111518`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `114211`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn below current price partial position burn 1`] = `116318`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #collect close to worst case 1`] = `52593`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #collect close to worst case, two tokens 1`] = `70312`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint above current price add to position existing 1`] = `126330`; +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`] = `280193`; +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`] = `143430`; +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`] = `151399`; +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`] = `358307`; +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`] = `168499`; +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`] = `126898`; +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`] = `354538`; +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`] = `143998`; +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`] = `61604`; +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`] = `149627`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `149772`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `149596`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `149741`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150320`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150465`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `166187`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `166332`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `199737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `199882`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `149694`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `149839`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `199737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `199882`; -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`] = `218937`; +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`] = `219082`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114094`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114239`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `114058`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `114203`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129839`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129984`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164210`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164355`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `226699`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `226844`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157227`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157372`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `149688`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `149833`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `149636`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `149781`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `114119`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `114264`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `180882`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `181166`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `180830`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `181114`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128213`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128497`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160126`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160259`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160095`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160228`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150557`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150690`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `176923`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `177056`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211184`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211317`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160193`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160326`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211184`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211317`; -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`] = `230384`; +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`] = `230517`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124593`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124726`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124557`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124690`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140575`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140708`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175657`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175790`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238146`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238279`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157464`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157597`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160198`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160331`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160146`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160279`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124629`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124762`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191392`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191664`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191340`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191612`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138723`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138995`; From 8c238e0a58fc2907ea7d376f7a010a02a11a118a Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 14 Aug 2024 17:54:56 +0300 Subject: [PATCH 23/60] [Plugin] split base plugin into abstract classes --- src/plugin/contracts/AlgebraBasePluginV1.sol | 290 +----------------- src/plugin/contracts/BasePluginV1Factory.sol | 2 +- src/plugin/contracts/base/BasePlugin.sol | 103 +++++++ .../interfaces/IAlgebraBasePluginV1.sol | 22 -- .../contracts/interfaces/IBasePlugin.sol | 18 ++ .../interfaces/plugins/IFarmingPlugin.sol | 4 - .../interfaces/plugins/IVolatilityOracle.sol | 4 + .../contracts/plugins/DynamicFeePlugin.sol | 76 +++++ .../contracts/plugins/FarmingProxyPlugin.sol | 83 +++++ .../contracts/plugins/SlidingFeePlugin.sol | 109 +++++++ .../plugins/VolatilityOraclePlugin.sol | 106 +++++++ src/plugin/contracts/test/MockObservable.sol | 4 + 12 files changed, 520 insertions(+), 301 deletions(-) create mode 100644 src/plugin/contracts/base/BasePlugin.sol delete mode 100644 src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol create mode 100644 src/plugin/contracts/interfaces/IBasePlugin.sol create mode 100644 src/plugin/contracts/plugins/DynamicFeePlugin.sol create mode 100644 src/plugin/contracts/plugins/FarmingProxyPlugin.sol create mode 100644 src/plugin/contracts/plugins/SlidingFeePlugin.sol create mode 100644 src/plugin/contracts/plugins/VolatilityOraclePlugin.sol diff --git a/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 8dde7e8b1..70fd8a3c1 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -1,253 +1,36 @@ // 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/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 './libraries/VolatilityOracle.sol'; -import './libraries/AdaptiveFee.sol'; -import './types/AlgebraFeeConfigurationU144.sol'; +import './plugins/DynamicFeePlugin.sol'; +import './plugins/FarmingProxyPlugin.sol'; +import './plugins/SlidingFeePlugin.sol'; +import './plugins/VolatilityOraclePlugin.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 { +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(); - } - - /// @inheritdoc IAlgebraBasePluginV1 - 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; - } - - // ###### 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'); - - 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; @@ -255,78 +38,37 @@ contract AlgebraBasePluginV1 is IAlgebraBasePluginV1, Timestamp, IAlgebraPlugin /// @dev unused function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external override onlyPool returns (bytes4, uint24) { - _updatePluginConfigInPool(); // should not be called, reset config + _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, uint24, uint24) { - _writeTimepointAndUpdateFee(); + uint88 volatilityAverage = _writeTimepoint(); + if(volatilityAverage != 0 ) _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); - } - } } diff --git a/src/plugin/contracts/BasePluginV1Factory.sol b/src/plugin/contracts/BasePluginV1Factory.sol index ad294e51b..d68331dec 100644 --- a/src/plugin/contracts/BasePluginV1Factory.sol +++ b/src/plugin/contracts/BasePluginV1Factory.sol @@ -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/base/BasePlugin.sol b/src/plugin/contracts/base/BasePlugin.sol new file mode 100644 index 000000000..43622a087 --- /dev/null +++ b/src/plugin/contracts/base/BasePlugin.sol @@ -0,0 +1,103 @@ +// 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.1 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 override 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 override virtual onlyPool returns (bytes4) { + return IAlgebraPlugin.beforeInitialize.selector; + } + + function afterInitialize(address, uint160, int24) external override virtual onlyPool returns (bytes4) { + return IAlgebraPlugin.afterInitialize.selector; + } + + function beforeModifyPosition(address, address, int24, int24, int128, bytes calldata) external override virtual onlyPool returns (bytes4, uint24) { + return (IAlgebraPlugin.beforeModifyPosition.selector, 0); + } + + function afterModifyPosition(address, address, int24, int24, int128, uint256, uint256, bytes calldata) external override virtual onlyPool returns (bytes4) { + return IAlgebraPlugin.afterModifyPosition.selector; + } + + function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override virtual onlyPool returns (bytes4, uint24, uint24) { + return (IAlgebraPlugin.beforeSwap.selector, 0, 0); + } + + function afterSwap(address, address, bool, int256, uint160, int256, int256, bytes calldata) external override virtual onlyPool returns (bytes4) { + return IAlgebraPlugin.afterSwap.selector; + } + + function beforeFlash(address, address, uint256, uint256, bytes calldata) external override virtual onlyPool returns (bytes4) { + return IAlgebraPlugin.beforeFlash.selector; + } + + function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes calldata) external override virtual onlyPool returns (bytes4) { + return IAlgebraPlugin.afterFlash.selector; + } + + // TODO + function _updatePluginConfigInPool(uint8 newPluginConfig) internal { + + (, , , uint8 currentPluginConfig) = _getPoolState(); + 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 84efe37a5..000000000 --- a/src/plugin/contracts/interfaces/IAlgebraBasePluginV1.sol +++ /dev/null @@ -1,22 +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; - - /// @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/IBasePlugin.sol b/src/plugin/contracts/interfaces/IBasePlugin.sol new file mode 100644 index 000000000..4730809d0 --- /dev/null +++ b/src/plugin/contracts/interfaces/IBasePlugin.sol @@ -0,0 +1,18 @@ +// 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; + + /// @notice Returns the address of the pool the plugin is created for + /// @return address of the pool + function pool() external view returns (address); +} diff --git a/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol b/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol index a9fceb33a..a5e43882f 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. 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/plugins/DynamicFeePlugin.sol b/src/plugin/contracts/plugins/DynamicFeePlugin.sol new file mode 100644 index 000000000..b84b3d434 --- /dev/null +++ b/src/plugin/contracts/plugins/DynamicFeePlugin.sol @@ -0,0 +1,76 @@ +// 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.1 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); + } + + // TODO + /// @inheritdoc IAlgebraDynamicFeePlugin + function getCurrentFee() external view override returns (uint16 fee) { + uint88 volatilityAverage; + 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..287a0a0a9 --- /dev/null +++ b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol @@ -0,0 +1,83 @@ +// 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.1 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_INIT_FLAG | 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) { + _updatePluginConfigInPool(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 { + _updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config + } + + } + +} diff --git a/src/plugin/contracts/plugins/SlidingFeePlugin.sol b/src/plugin/contracts/plugins/SlidingFeePlugin.sol new file mode 100644 index 000000000..5556f41fb --- /dev/null +++ b/src/plugin/contracts/plugins/SlidingFeePlugin.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.20; + +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 'hardhat/console.sol'; + +abstract contract SlidingFeeModule is Timestamp { + struct FeeFactors { + uint128 zeroToOneFeeFactor; + uint128 oneToZeroFeeFactor; + } + + uint64 internal constant FEE_FACTOR_SHIFT = 96; + + FeeFactors public s_feeFactors; + + uint256 public s_priceChangeFactor = 1; + + event PriceChangeFactor(uint256 priceChangeFactor); + + constructor() { + FeeFactors memory feeFactors = FeeFactors( + uint128(1 << FEE_FACTOR_SHIFT), + uint128(1 << FEE_FACTOR_SHIFT) + ); + + s_feeFactors = feeFactors; + } + + function _getFeeAndUpdateFactors( + int24 currenTick, + int24 lastTick, + uint16 poolFee, + bool zeroToOne + ) internal returns (uint16) { + FeeFactors memory currentFeeFactors; + + // console.log('current price: ', currentPrice); + // console.log('last price: ', lastPrice); + // console.log('zero to one: ', zeroToOne); + + // ❗❗❗ + // раньше было currentPrice = 0, я так понял проверка на то, инициализирована ли была цена + // теперь currentTick = 0 - валидное значение, возможно стоит передавать доп аргумент + if (lastTick == 0) { + return poolFee; + } + + currentFeeFactors = _calculateFeeFactors(currenTick, lastTick); + + s_feeFactors = currentFeeFactors; + + uint16 adjustedFee = zeroToOne ? + uint16((poolFee * currentFeeFactors.zeroToOneFeeFactor) >> FEE_FACTOR_SHIFT) : + uint16((poolFee * currentFeeFactors.oneToZeroFeeFactor) >> FEE_FACTOR_SHIFT); + + return adjustedFee; + } + + function _calculateFeeFactors( + int24 currentTick, + int24 lastTick + ) internal view returns (FeeFactors memory feeFactors) { + console.log('currentTick: '); + console.logInt(int256(currentTick)); + console.log('lastTick: '); + console.logInt(int256(lastTick)); + // price change is positive after zeroToOne prevalence + int256 priceChangeRatio = int256(uint256(TickMath.getSqrtRatioAtTick(currentTick - lastTick))) - int256(1 << FEE_FACTOR_SHIFT); // (currentPrice - lastPrice) / lastPrice + console.log('priceChangeRatio: '); + console.logInt(priceChangeRatio); + int128 feeFactorImpact = int128(priceChangeRatio * int256(s_priceChangeFactor)); + + feeFactors = s_feeFactors; + + // if there were zeroToOne prevalence in the last price change, + // in result price has increased + // we need to increase zeroToOneFeeFactor + // and vice versa + int128 newZeroToOneFeeFactor = int128(feeFactors.zeroToOneFeeFactor) + feeFactorImpact; + + if ((int128(-2) << FEE_FACTOR_SHIFT) < newZeroToOneFeeFactor && newZeroToOneFeeFactor < int128(uint128(2) << FEE_FACTOR_SHIFT)) { + feeFactors = FeeFactors( + uint128(newZeroToOneFeeFactor), + uint128(int128(feeFactors.oneToZeroFeeFactor) - 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( + uint128(2 << FEE_FACTOR_SHIFT), + 0 + ); + } 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( + 0, + uint128(2 << FEE_FACTOR_SHIFT) + ); + } + } +} \ No newline at end of file diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol new file mode 100644 index 000000000..e63e39ee6 --- /dev/null +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -0,0 +1,106 @@ +// 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.1 default 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; + + _updatePluginConfigInPool(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 returns(uint88 volatilityAverage) { + // single SLOAD + uint16 _lastIndex = timepointIndex; + uint32 _lastTimepointTimestamp = lastTimepointTimestamp; + + bool _isInitialized = isInitialized; + require(_isInitialized, 'Not initialized'); + + uint32 currentTimestamp = _blockTimestamp(); + // TODO + if (_lastTimepointTimestamp == currentTimestamp) return 0; + + (, int24 tick, , ) = _getPoolState(); + (uint16 newLastIndex, uint16 newOldestIndex) = timepoints.write(_lastIndex, currentTimestamp, tick); + + volatilityAverage = timepoints.getAverageVolatility(currentTimestamp, tick, newLastIndex, newOldestIndex); + + } +} 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 { + + } } From f574d3c5e88c8ec560fe0fb11d819511407a6f0e Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 15 Aug 2024 14:43:24 +0300 Subject: [PATCH 24/60] [Plugin] add volatility getter to twap plugin --- src/plugin/contracts/AlgebraBasePluginV1.sol | 10 ++++++++-- .../contracts/plugins/DynamicFeePlugin.sol | 5 +---- .../plugins/VolatilityOraclePlugin.sol | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 70fd8a3c1..194075bca 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -49,8 +49,9 @@ contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, Volatility } function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4, uint24, uint24) { - uint88 volatilityAverage = _writeTimepoint(); - if(volatilityAverage != 0 ) _updateFee(volatilityAverage); + _writeTimepoint(); + uint88 volatilityAverage = _getAverageVolatility(); + _updateFee(volatilityAverage); return (IAlgebraPlugin.beforeSwap.selector, 0, 0); } @@ -71,4 +72,9 @@ contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, Volatility return IAlgebraPlugin.afterFlash.selector; } + function getCurrentFee() external view override returns(uint16 fee) { + uint88 volatilityAverage = _getAverageVolatility(); + fee =_getCurrentFee(volatilityAverage); + } + } diff --git a/src/plugin/contracts/plugins/DynamicFeePlugin.sol b/src/plugin/contracts/plugins/DynamicFeePlugin.sol index b84b3d434..77b6ea4b9 100644 --- a/src/plugin/contracts/plugins/DynamicFeePlugin.sol +++ b/src/plugin/contracts/plugins/DynamicFeePlugin.sol @@ -48,10 +48,7 @@ abstract contract DynamicFeePlugin is BasePlugin, IDynamicFeeManager { emit FeeConfiguration(_config); } - // TODO - /// @inheritdoc IAlgebraDynamicFeePlugin - function getCurrentFee() external view override returns (uint16 fee) { - uint88 volatilityAverage; + function _getCurrentFee(uint88 volatilityAverage) internal view returns (uint16 fee) { AlgebraFeeConfigurationU144 feeConfig_ = _feeConfig; if (feeConfig_.alpha1() | feeConfig_.alpha2() == 0) return feeConfig_.baseFee(); diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol index e63e39ee6..f67b8cd50 100644 --- a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -85,7 +85,7 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ } } - function _writeTimepoint() internal returns(uint88 volatilityAverage) { + function _writeTimepoint() internal { // single SLOAD uint16 _lastIndex = timepointIndex; uint32 _lastTimepointTimestamp = lastTimepointTimestamp; @@ -94,13 +94,21 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ require(_isInitialized, 'Not initialized'); uint32 currentTimestamp = _blockTimestamp(); - // TODO - if (_lastTimepointTimestamp == currentTimestamp) return 0; + if (_lastTimepointTimestamp == currentTimestamp) return; (, int24 tick, , ) = _getPoolState(); - (uint16 newLastIndex, uint16 newOldestIndex) = timepoints.write(_lastIndex, currentTimestamp, tick); + timepoints.write(_lastIndex, currentTimestamp, tick); - volatilityAverage = timepoints.getAverageVolatility(currentTimestamp, tick, newLastIndex, newOldestIndex); + } + + function _getAverageVolatility() 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); } } From 008f30e6f90831b4a0a2c4cefd8ab67d780de128 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 15 Aug 2024 14:45:18 +0300 Subject: [PATCH 25/60] [Plugin] fix farming plugin default config --- src/plugin/contracts/plugins/FarmingProxyPlugin.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol index 287a0a0a9..3c4ec065b 100644 --- a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol +++ b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol @@ -16,7 +16,7 @@ import '../base/BasePlugin.sol'; abstract contract FarmingProxyPlugin is BasePlugin, IFarmingPlugin { using Plugins for uint8; - uint8 private constant defaultPluginConfig = uint8(Plugins.AFTER_INIT_FLAG | Plugins.AFTER_SWAP_FLAG); + uint8 private constant defaultPluginConfig = uint8(Plugins.AFTER_SWAP_FLAG); /// @inheritdoc IFarmingPlugin address public override incentive; From 6edd5920c8653dea1c054d0189b4befdfbf0b9f1 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 15 Aug 2024 17:27:07 +0300 Subject: [PATCH 26/60] [Plugin] change default plugin config --- src/plugin/contracts/AlgebraBasePluginV1.sol | 6 +++--- src/plugin/contracts/base/BasePlugin.sol | 17 ++++++++++++++++- .../contracts/plugins/FarmingProxyPlugin.sol | 4 ++-- .../plugins/VolatilityOraclePlugin.sol | 6 ++---- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 194075bca..53d71da42 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -16,7 +16,7 @@ contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, Volatility using Plugins for uint8; /// @inheritdoc IAlgebraPlugin - uint8 public constant override defaultPluginConfig = uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG | Plugins.DYNAMIC_FEE); + 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) { @@ -50,7 +50,7 @@ contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, Volatility function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4, uint24, uint24) { _writeTimepoint(); - uint88 volatilityAverage = _getAverageVolatility(); + uint88 volatilityAverage = _getAverageVolatilityLast(); _updateFee(volatilityAverage); return (IAlgebraPlugin.beforeSwap.selector, 0, 0); } @@ -73,7 +73,7 @@ contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, Volatility } function getCurrentFee() external view override returns(uint16 fee) { - uint88 volatilityAverage = _getAverageVolatility(); + uint88 volatilityAverage = _getAverageVolatilityLast(); fee =_getCurrentFee(volatilityAverage); } diff --git a/src/plugin/contracts/base/BasePlugin.sol b/src/plugin/contracts/base/BasePlugin.sol index 43622a087..93850a00c 100644 --- a/src/plugin/contracts/base/BasePlugin.sol +++ b/src/plugin/contracts/base/BasePlugin.sol @@ -91,7 +91,6 @@ abstract contract BasePlugin is IBasePlugin, Timestamp { return IAlgebraPlugin.afterFlash.selector; } - // TODO function _updatePluginConfigInPool(uint8 newPluginConfig) internal { (, , , uint8 currentPluginConfig) = _getPoolState(); @@ -100,4 +99,20 @@ abstract contract BasePlugin is IBasePlugin, Timestamp { } } + 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/plugins/FarmingProxyPlugin.sol b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol index 3c4ec065b..4e54da36f 100644 --- a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol +++ b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol @@ -55,7 +55,7 @@ abstract contract FarmingProxyPlugin is BasePlugin, IFarmingPlugin { } if (isPluginConnected) { - _updatePluginConfigInPool(defaultPluginConfig); + _enablePluginFlags(defaultPluginConfig); } } @@ -75,7 +75,7 @@ abstract contract FarmingProxyPlugin is BasePlugin, IFarmingPlugin { (, int24 tick, , ) = _getPoolState(); IAlgebraVirtualPool(_incentive).crossTo(tick, zeroToOne); } else { - _updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config + _disablePluginFlags(defaultPluginConfig); // should not be called, reset config } } diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol index f67b8cd50..e7b65827f 100644 --- a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -10,7 +10,6 @@ import '../interfaces/plugins/IVolatilityOracle.sol'; import '../libraries/VolatilityOracle.sol'; import '../base/BasePlugin.sol'; - /// @title Algebra Integral 1.1 default plugin /// @notice This contract stores timepoints and calculates adaptive fee and statistical averages abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ @@ -50,7 +49,7 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ lastTimepointTimestamp = time; isInitialized = true; - _updatePluginConfigInPool(defaultPluginConfig); + _enablePluginFlags(defaultPluginConfig); } // ###### Volatility and TWAP oracle ###### @@ -98,10 +97,9 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ (, int24 tick, , ) = _getPoolState(); timepoints.write(_lastIndex, currentTimestamp, tick); - } - function _getAverageVolatility() internal view returns (uint88 volatilityAverage) { + function _getAverageVolatilityLast() internal view returns (uint88 volatilityAverage) { uint32 currentTimestamp = _blockTimestamp(); (, int24 tick, , ) = _getPoolState(); From 542fe2f37d7084a162a8e3b18f1ea5f011a568a9 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 15 Aug 2024 17:40:43 +0300 Subject: [PATCH 27/60] [Plugin] fix volatility oracle plugin --- src/plugin/contracts/plugins/VolatilityOraclePlugin.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol index e7b65827f..97ed661a4 100644 --- a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -96,7 +96,10 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ if (_lastTimepointTimestamp == currentTimestamp) return; (, int24 tick, , ) = _getPoolState(); - timepoints.write(_lastIndex, currentTimestamp, tick); + (uint16 newLastIndex, ) = timepoints.write(_lastIndex, currentTimestamp, tick); + + timepointIndex = newLastIndex; + lastTimepointTimestamp = currentTimestamp; } function _getAverageVolatilityLast() internal view returns (uint88 volatilityAverage) { From 3596dc76803ed2c9381f8f096869677d82a55fc3 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 15 Aug 2024 19:05:45 +0300 Subject: [PATCH 28/60] [Plugin] update snapshots --- .../AlgebraBasePluginV1.spec.ts.snap | 2 +- .../AlgebraPool.gas.spec.ts.snap | 100 +++++++++--------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap index 3a2ab609a..369d2135e 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`] = `23779`; +exports[`AlgebraBasePluginV1 AlgebraBasePluginV1 external methods #changeFeeConfiguration feeConfig getter gas cost [ @skip-on-coverage ] 1`] = `23774`; diff --git a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 13a477198..ef3c1b4a2 100644 --- a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -1,24 +1,24 @@ // 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`] = `208344`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee large swap crossing several initialized ticks 1`] = `216020`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `160359`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle 1`] = `168035`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `195797`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 4h 1`] = `203473`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `188583`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 8h 1`] = `196259`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `156851`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps dynamic fee small swap with filled volatilityOracle after 24h 1`] = `164527`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `205458`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee large swap crossing several initialized ticks 1`] = `214790`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `155205`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle 1`] = `164537`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `188670`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 4h 1`] = `198002`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `199604`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 8h 1`] = `208936`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `154677`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] Filled VolatilityOracle swaps static fee small swap with filled volatilityOracle after 24h 1`] = `164009`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #burn above current price burn when only position using ticks 1`] = `117065`; @@ -62,82 +62,82 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below curre 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`] = `149772`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `157448`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `149741`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement 1`] = `157417`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150465`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `159202`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `166332`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `174008`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `199882`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `207558`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `149839`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `157515`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `199882`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `207558`; -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`] = `219082`; +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`] = `226758`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114239`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `127681`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `114203`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block with no tick movement 1`] = `127645`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129984`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `143426`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164355`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `177797`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `226844`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 several large swaps with pauses 1`] = `234520`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157372`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `165048`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `149833`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `157509`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `149781`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 first swap in block with no tick movement 1`] = `157457`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `114264`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap #swapExact1For0 second swap in block with no tick movement 1`] = `127706`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `181166`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `188859`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `181114`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected first swap in block with no tick movement 1`] = `188807`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `128497`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swap farming connected second swap in block with no tick movement 1`] = `141956`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `160259`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `167935`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `160228`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement 1`] = `167904`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `150690`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block with no tick movement, static fee 1`] = `159427`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `177056`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `184732`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `211317`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `218993`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `160326`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `168002`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `211317`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `218993`; -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`] = `230517`; +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`] = `238193`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `124726`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `138168`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `124690`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block with no tick movement 1`] = `138132`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `140708`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `154150`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `175790`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `189232`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `238279`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 several large swaps with pauses 1`] = `245955`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `157597`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact0For1 small swap after several large swaps with pauses 1`] = `165273`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `160331`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `168007`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `160279`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 first swap in block with no tick movement 1`] = `167955`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `124762`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap #swapExact1For0 second swap in block with no tick movement 1`] = `138204`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `191664`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block moves tick, no initialized crossings 1`] = `199357`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `191612`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected first swap in block with no tick movement 1`] = `199305`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `138995`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swap farming connected second swap in block with no tick movement 1`] = `152454`; From 2d221db0f764c37c81c8ab243b1ff78e1310387f Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 23 Aug 2024 20:02:21 +0300 Subject: [PATCH 29/60] [Plugin] add elasticFee plugin and basePluginV2 --- src/plugin/contracts/AlgebraBasePluginV2.sol | 82 +++++++++++++++++++ .../contracts/plugins/SlidingFeePlugin.sol | 29 ++----- .../plugins/VolatilityOraclePlugin.sol | 5 ++ 3 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 src/plugin/contracts/AlgebraBasePluginV2.sol diff --git a/src/plugin/contracts/AlgebraBasePluginV2.sol b/src/plugin/contracts/AlgebraBasePluginV2.sol new file mode 100644 index 000000000..ae1a9bbdf --- /dev/null +++ b/src/plugin/contracts/AlgebraBasePluginV2.sol @@ -0,0 +1,82 @@ +// 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.1 default plugin +/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +contract AlgebraBasePluginV1 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, uint16 fee, ) = _getPoolState(); + int24 lastTick = _getLastTick(); + uint16 newFee = _getFeeAndUpdateFactors(zeroToOne, currentTick, lastTick, fee); + if (newFee != fee) { + IAlgebraPool(pool).setFee(newFee); + } + + _writeTimepoint(); + return (IAlgebraPlugin.beforeSwap.selector, 0, 0); + } + + function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes calldata) external override onlyPool returns (bytes4) { + _updateVirtualPoolTick(zeroToOne); + return IAlgebraPlugin.afterSwap.selector; + } + + /// @dev unused + function beforeFlash(address, address, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) { + _updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config + return IAlgebraPlugin.beforeFlash.selector; + } + + /// @dev unused + function afterFlash(address, address, uint256, uint256, uint256, uint256, bytes calldata) external override onlyPool returns (bytes4) { + _updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config + return IAlgebraPlugin.afterFlash.selector; + } + + function getCurrentFee() external view returns(uint16 fee) { + ( , , fee, ) = _getPoolState(); + } + +} diff --git a/src/plugin/contracts/plugins/SlidingFeePlugin.sol b/src/plugin/contracts/plugins/SlidingFeePlugin.sol index 5556f41fb..a57134bff 100644 --- a/src/plugin/contracts/plugins/SlidingFeePlugin.sol +++ b/src/plugin/contracts/plugins/SlidingFeePlugin.sol @@ -5,9 +5,9 @@ import {IAlgebraPool} from '@cryptoalgebra/integral-core/contracts/interfaces/IA import {Timestamp} from '@cryptoalgebra/integral-core/contracts/base/common/Timestamp.sol'; import {TickMath} from '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol'; -import 'hardhat/console.sol'; +import {BasePlugin} from '../base/BasePlugin.sol'; -abstract contract SlidingFeeModule is Timestamp { +abstract contract SlidingFeePlugin is BasePlugin { struct FeeFactors { uint128 zeroToOneFeeFactor; uint128 oneToZeroFeeFactor; @@ -31,25 +31,14 @@ abstract contract SlidingFeeModule is Timestamp { } function _getFeeAndUpdateFactors( - int24 currenTick, + bool zeroToOne, + int24 currentTick, int24 lastTick, - uint16 poolFee, - bool zeroToOne + uint16 poolFee ) internal returns (uint16) { FeeFactors memory currentFeeFactors; - // console.log('current price: ', currentPrice); - // console.log('last price: ', lastPrice); - // console.log('zero to one: ', zeroToOne); - - // ❗❗❗ - // раньше было currentPrice = 0, я так понял проверка на то, инициализирована ли была цена - // теперь currentTick = 0 - валидное значение, возможно стоит передавать доп аргумент - if (lastTick == 0) { - return poolFee; - } - - currentFeeFactors = _calculateFeeFactors(currenTick, lastTick); + currentFeeFactors = _calculateFeeFactors(currentTick, lastTick); s_feeFactors = currentFeeFactors; @@ -64,14 +53,8 @@ abstract contract SlidingFeeModule is Timestamp { int24 currentTick, int24 lastTick ) internal view returns (FeeFactors memory feeFactors) { - console.log('currentTick: '); - console.logInt(int256(currentTick)); - console.log('lastTick: '); - console.logInt(int256(lastTick)); // price change is positive after zeroToOne prevalence int256 priceChangeRatio = int256(uint256(TickMath.getSqrtRatioAtTick(currentTick - lastTick))) - int256(1 << FEE_FACTOR_SHIFT); // (currentPrice - lastPrice) / lastPrice - console.log('priceChangeRatio: '); - console.logInt(priceChangeRatio); int128 feeFactorImpact = int128(priceChangeRatio * int256(s_priceChangeFactor)); feeFactors = s_feeFactors; diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol index 97ed661a4..f5bef6727 100644 --- a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -112,4 +112,9 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ volatilityAverage = timepoints.getAverageVolatility(currentTimestamp, tick, lastTimepointIndex, oldestIndex); } + + function _getLastTick() internal view returns(int24 lastTick) { + VolatilityOracle.Timepoint memory lastTimepoint = timepoints[timepointIndex]; + return lastTimepoint.tick; + } } From 2a65fdf48247f80f6e572270563daf13640e64f1 Mon Sep 17 00:00:00 2001 From: debych Date: Tue, 3 Sep 2024 20:17:19 +0300 Subject: [PATCH 30/60] [Core] fix pending fee transfer --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/base/ReservesManager.sol | 86 ++++++++++++------- .../contracts/libraries/PoolAddress.sol | 2 +- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 6236ce8f7..6ddd35907 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x53a254b73c7f4f4a23175de0908ad4b30f3bc60806bd69bba905db6f24b991a5; + bytes32 public constant POOL_INIT_CODE_HASH = 0xe5353bc2362696b562faf4b8c41096ecbc2795f6f42dd5b78d8750e17b84ad8d; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index 488e24ef9..d1aa3dc11 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -67,15 +67,13 @@ abstract contract ReservesManager is AlgebraPoolBase { } } - function updateFeeAmounts( - int256 deltaR0, - int256 deltaR1, + function _accrueAndTransferFees( uint256 fee0, uint256 fee1, - address feesRecipient, - bytes32 slot, - uint32 lastTimestamp - ) internal returns (int256, int256, uint256, uint256) { + uint256 lastTimestamp, + address recipient, + bytes32 slot + ) internal returns (uint256, uint256, uint256, uint256) { uint256 feePending0; uint256 feePending1; @@ -93,29 +91,35 @@ abstract contract ReservesManager is AlgebraPoolBase { feePending0 += fee0; feePending1 += fee1; - uint256 feeSent0; - uint256 feeSent1; - if (_blockTimestamp() - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY || feePending0 > type(uint104).max || feePending1 > type(uint104).max) { - if (feePending0 > 0) { - _transfer(token0, feesRecipient, feePending0); - deltaR0 = deltaR0 - feePending0.toInt256(); - feeSent0 = feePending0; - feePending0 = 0; - } - if (feePending1 > 0) { - _transfer(token1, feesRecipient, feePending1); - deltaR1 = deltaR1 - feePending1.toInt256(); - feeSent1 = feePending1; - feePending1 = 0; + (uint256 feeSent0, uint256 feeSent1) = _transferFees(feePending0, feePending1, recipient); + assembly { + sstore(slot, 0) } + return (0, 0, feeSent0, feeSent1); + } else { + return (feePending0, feePending1, 0, 0); } + } - assembly { - sstore(slot, or(or(feePending0, shl(104, feePending1)), shl(208, lastTimestamp))) + 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 (deltaR0, deltaR1, feeSent0, feeSent1); + return (feeSent0, feeSent1); } /// @notice Applies deltas to reserves and pays communityFees @@ -136,26 +140,46 @@ abstract contract ReservesManager is AlgebraPoolBase { bytes32 slot; uint32 lastTimestamp = lastFeeTransferTimestamp; uint32 currentTimestamp = _blockTimestamp(); + bool feeSent; if (communityFee0 | communityFee1 != 0) { assembly { slot := communityFeePending0.slot } - (deltaR0, deltaR1, , ) = updateFeeAmounts(deltaR0, deltaR1, communityFee0, communityFee1, communityVault, slot, lastTimestamp); + + (uint256 feePending0, uint256 feePending1, uint256 feeSent0, uint256 feeSent1) = _accrueAndTransferFees(communityFee0, communityFee1, lastTimestamp, communityVault, slot); + if (feeSent0 | feeSent1 != 0) { + (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); + feeSent = true; + } else { + (communityFeePending0, communityFeePending1) = (uint104(feePending0), uint104(feePending1)); + } } if (pluginFee0 | pluginFee1 != 0) { assembly { slot := pluginFeePending0.slot } - uint256 pluginFeeSent0; - uint256 pluginFeeSent1; - (deltaR0, deltaR1, pluginFeeSent0, pluginFeeSent1) = updateFeeAmounts(deltaR0, deltaR1, pluginFee0, pluginFee1, plugin, slot, lastTimestamp); - if (pluginFeeSent0 > 0 || pluginFeeSent1 > 0) { - if (IAlgebraPlugin(plugin).handlePluginFee(pluginFeeSent0, pluginFeeSent1) != IAlgebraPlugin.handlePluginFee.selector) revert IAlgebraPoolErrors.invalidPluginResponce(); + + (uint256 feePending0, uint256 feePending1, uint256 feeSent0, uint256 feeSent1) = _accrueAndTransferFees(pluginFee0, pluginFee1, lastTimestamp, plugin, slot); + if (feeSent0 | feeSent1 != 0) { + (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); + feeSent = true; + + if (IAlgebraPlugin(plugin).handlePluginFee(feeSent0, feeSent1) != IAlgebraPlugin.handlePluginFee.selector) revert IAlgebraPoolErrors.invalidPluginResponce(); + } else { + (pluginFeePending0, pluginFeePending1) = (uint104(feePending0), uint104(feePending1)); + } + } else if (feeSent) { + (uint256 feeSent0, uint256 feeSent1) = _transferFees(pluginFeePending0, pluginFeePending1, plugin); + if (feeSent0 | feeSent1 != 0) { + (pluginFeePending0, pluginFeePending1) = (0, 0); + (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); + + if (IAlgebraPlugin(plugin).handlePluginFee(feeSent0, feeSent1) != IAlgebraPlugin.handlePluginFee.selector) revert IAlgebraPoolErrors.invalidPluginResponce(); } } - if (currentTimestamp - lastTimestamp >= Constants.FEE_TRANSFER_FREQUENCY) lastFeeTransferTimestamp = currentTimestamp; + if (feeSent) lastFeeTransferTimestamp = currentTimestamp; } if (deltaR0 | deltaR1 == 0) return; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 770ef4d48..b0f4804f0 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 = 0x53a254b73c7f4f4a23175de0908ad4b30f3bc60806bd69bba905db6f24b991a5; + bytes32 internal constant POOL_INIT_CODE_HASH = 0xe5353bc2362696b562faf4b8c41096ecbc2795f6f42dd5b78d8750e17b84ad8d; /// @notice The identifying key of the pool struct PoolKey { From 2f5387f8cb7296e6833ab76ecce1a563a8f0c03f Mon Sep 17 00:00:00 2001 From: debych Date: Tue, 3 Sep 2024 20:17:52 +0300 Subject: [PATCH 31/60] [Core] set 0 runs --- src/core/hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/hardhat.config.ts b/src/core/hardhat.config.ts index b130f2216..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: 500, + runs: 0, }, metadata: { bytecodeHash: 'none', From d6b2c7734a75fcf8f2e98b8d5f7d6abb2cfccd9a Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 4 Sep 2024 17:44:53 +0300 Subject: [PATCH 32/60] [Core] add changeReserves to burn --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 11 +++++++---- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 6ddd35907..be7536a2e 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0xe5353bc2362696b562faf4b8c41096ecbc2795f6f42dd5b78d8750e17b84ad8d; + bytes32 public constant POOL_INIT_CODE_HASH = 0x6385c295f92db035f35dfbf362b0b49aa3525ab9a8f623f2c137edbe2b3d3574; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 1663dea28..dcfe0c5bf 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -143,16 +143,19 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio (amount0, amount1) = _updatePositionTicksAndFees(position, bottomTick, topTick, liquidityDelta); if (pluginFee > 0) { + uint256 deltaPluginFeePending0; + uint256 deltaPluginFeePending1; + if (amount0 > 0) { - uint256 deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR); + deltaPluginFeePending0 = FullMath.mulDiv(amount0, pluginFee, Constants.FEE_DENOMINATOR); amount0 -= deltaPluginFeePending0; - pluginFeePending0 += uint104(deltaPluginFeePending0); } if (amount1 > 0) { - uint256 deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR); + deltaPluginFeePending1 = FullMath.mulDiv(amount1, pluginFee, Constants.FEE_DENOMINATOR); amount1 -= deltaPluginFeePending1; - pluginFeePending1 += uint104(deltaPluginFeePending1); } + + _changeReserves(0, 0, 0, 0, deltaPluginFeePending0, deltaPluginFeePending1); } if (amount0 | amount1 != 0) { diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index b0f4804f0..afdeb260c 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 = 0xe5353bc2362696b562faf4b8c41096ecbc2795f6f42dd5b78d8750e17b84ad8d; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x6385c295f92db035f35dfbf362b0b49aa3525ab9a8f623f2c137edbe2b3d3574; /// @notice The identifying key of the pool struct PoolKey { From 3111145092b52a2efb745baa11cc3a670f2f1f7e Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 4 Sep 2024 22:04:41 +0300 Subject: [PATCH 33/60] [Core] update fees calculation --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 2 +- src/core/contracts/base/SwapCalculation.sol | 7 ++++++- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index be7536a2e..7c99afbe0 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x6385c295f92db035f35dfbf362b0b49aa3525ab9a8f623f2c137edbe2b3d3574; + bytes32 public constant POOL_INIT_CODE_HASH = 0x95cae1693e5a3bc78f48679682e43b3ce024da4362d51eccdac32de2ccaba0ba; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index dcfe0c5bf..18cdfa1eb 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -373,7 +373,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio if (_isPlugin()) return (0, 0); bytes4 selector; (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data); - if (overrideFee >= 1e6 || pluginFee > overrideFee) revert incorrectPluginFee(); + if ((overrideFee + pluginFee) >= 1e6) revert incorrectPluginFee(); selector.shouldReturn(IAlgebraPlugin.beforeSwap.selector); } } diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index 75fb28a62..f00fb196d 100644 --- a/src/core/contracts/base/SwapCalculation.sol +++ b/src/core/contracts/base/SwapCalculation.sol @@ -60,7 +60,12 @@ 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; + if (overrideFee != 0) { + cache.fee = overrideFee + pluginFee; + } else { + cache.fee += pluginFee; + } + if (cache.fee >= 1e6) revert incorrectPluginFee(); if (zeroToOne) { if (limitSqrtPrice >= currentPrice || limitSqrtPrice <= TickMath.MIN_SQRT_RATIO) revert invalidLimitSqrtPrice(); diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index afdeb260c..f991d8572 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 = 0x6385c295f92db035f35dfbf362b0b49aa3525ab9a8f623f2c137edbe2b3d3574; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x95cae1693e5a3bc78f48679682e43b3ce024da4362d51eccdac32de2ccaba0ba; /// @notice The identifying key of the pool struct PoolKey { From 0245da0a75dfab2a430a40fce51e91fcc76b5f0c Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 5 Sep 2024 12:59:21 +0300 Subject: [PATCH 34/60] [Core] fix tests --- src/core/contracts/test/MockPoolPlugin.sol | 4 +-- src/core/test/AlgebraPool.spec.ts | 36 +++++++++++++--------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/core/contracts/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index 27ebf15db..b3675f9aa 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -110,8 +110,8 @@ contract MockPoolPlugin is IAlgebraPlugin, IAlgebraDynamicFeePlugin { ) 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, overrideFee); - return (IAlgebraPlugin.defaultPluginConfig.selector, overrideFee); + return (IAlgebraPlugin.beforeModifyPosition.selector, pluginFee); + return (IAlgebraPlugin.defaultPluginConfig.selector, pluginFee); } /// @notice The hook called after a position is modified diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index f5bac29d9..0526c7c37 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2430,7 +2430,7 @@ describe('AlgebraPool', () => { }); }); - describe('#pluginFee', () => { + describe.only('#pluginFee', () => { let poolPlugin : MockPoolPlugin; beforeEach('initialize the pool', async () => { @@ -2442,15 +2442,19 @@ describe('AlgebraPool', () => { await mint(wallet.address, minTick, maxTick, expandTo18Decimals(1)); }); - it('swap fails if plugin fee greater than override fee', async () => { - await poolPlugin.setPluginFees(3000, 4000); + 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 plugin fee exceeds max value', async () => { - await poolPlugin.setPluginFees(1000000, 4000); + 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'); - await expect(pool.burn(minTick, maxTick, expandTo18Decimals(1), '0x')).to.be.revertedWithCustomError(pool, 'incorrectPluginFee'); }) it('swap fails if plugin return incorrect selector', async () => { @@ -2470,29 +2474,32 @@ describe('AlgebraPool', () => { }) it('works correct on swap, fee is 50%, 75%, 99%', async () => { - await poolPlugin.setPluginFees(500000, 500000); + 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(750000, 750000); + 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(990000, 990000); + 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(6000, 0); + 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') - let pluginFees = await pool.getPluginFeePending(); - expect(pluginFees[0]).to.be.eq(6n * 10n**15n-1n); - expect(pluginFees[1]).to.be.eq(6n * 10n**15n-1n) + 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('fees transfered to plugin', async () => { @@ -2506,8 +2513,9 @@ describe('AlgebraPool', () => { expect(pluginBalance1After - pluginBalance1Before).to.be.eq(0); }) + it('works correct with communityFee', async () => { - await poolPlugin.setPluginFees(5000, 4000); + await poolPlugin.setPluginFees(1000, 4000); await pool.setCommunityFee(500); await swapExact0For1(expandTo18Decimals(1), wallet.address); await swapExact0For1(expandTo18Decimals(1), wallet.address); From ccf54f964067fb047bccac2c72b12a649893672c Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 5 Sep 2024 13:49:38 +0300 Subject: [PATCH 35/60] [Core] fix tests & updates snapshots --- src/core/test/AlgebraPool.gas.spec.ts | 30 ++++++- src/core/test/AlgebraPool.spec.ts | 3 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +-- .../AlgebraPool.gas.spec.ts.snap | 80 +++++++++++-------- 4 files changed, 81 insertions(+), 42 deletions(-) diff --git a/src/core/test/AlgebraPool.gas.spec.ts b/src/core/test/AlgebraPool.gas.spec.ts index 97dbba344..0f2e46f33 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'; @@ -122,6 +122,34 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { }); }) + describe('#swapExact1For0 with plugin fee on', () => { + + beforeEach('load the fixture', async () => { + const MockPoolPluginFactory = await ethers.getContractFactory('MockPoolPlugin'); + let poolPlugin = (await MockPoolPluginFactory.deploy(await pool.getAddress())) as any as MockPoolPlugin; + await poolPlugin.setPluginFees(0, 1000); + await pool.setPlugin(poolPlugin); + await pool.setPluginConfig(255); + }); + + it('first swap in block with no tick movement, without transfer', async () => { + await swapExact1For0(1000, wallet.address) + await pool.advanceTime(1) + await snapshotGasCost(swapExact1For0(1000, wallet.address)); + }); + + it('first swap in block with no tick movement, with transfer', async () => { + await snapshotGasCost(swapExact1For0(10000, wallet.address)); + }); + + it('first swap in block moves tick, no initialized crossings, with transfer', async () => { + await swapExact1For0(1000, wallet.address); + await pool.advanceTime(86400) + await snapshotGasCost(swapExact1For0(expandTo18Decimals(1) / 10000n, wallet.address)); + }); + + }) + describe('#swapExact0For1', () => { it('first swap in block with no tick movement', async () => { await snapshotGasCost(swapExact0For1(2000, wallet.address)); diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 0526c7c37..31f0f1925 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2430,7 +2430,7 @@ describe('AlgebraPool', () => { }); }); - describe.only('#pluginFee', () => { + describe('#pluginFee', () => { let poolPlugin : MockPoolPlugin; beforeEach('initialize the pool', async () => { @@ -2513,7 +2513,6 @@ describe('AlgebraPool', () => { expect(pluginBalance1After - pluginBalance1Before).to.be.eq(0); }) - it('works correct with communityFee', async () => { await poolPlugin.setPluginFees(1000, 4000); await pool.setCommunityFee(500); diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 6a713244d..b6088c8f7 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`] = `4796400`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4802891`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4796400`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4802891`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4784203`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4790694`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4784203`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4790694`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10699`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22658`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22687`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 23776de62..567165398 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -58,39 +58,45 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below curr 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`] = `103318`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103487`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103287`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103456`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119063`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119232`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152577`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152746`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103456`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103625`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152577`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152746`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171777`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171946`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103318`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103487`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103282`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103451`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119887`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `120056`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153428`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153597`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171777`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171946`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103156`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103325`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103152`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103321`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103379`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103548`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103327`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103496`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103343`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103512`; + +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`] = `141761`; + +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`] = `175909`; + +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`] = `148849`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117084`; @@ -146,36 +152,42 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre 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 moves tick, no initialized crossings 1`] = `113817`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `114074`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103693`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `130056`; + +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164281`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103524`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `114212`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129799`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164281`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164024`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183481`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `113955`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114074`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164024`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103688`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183224`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130880`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113817`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `165132`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103519`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183481`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130623`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103562`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164875`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103558`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183224`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `114146`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103393`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103733`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103389`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103749`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `113889`; +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`] = `185944`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103564`; +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`] = `149086`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103580`; +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`] = `131986`; From fccb22b04b051c49856ededdf4b81943a40db602 Mon Sep 17 00:00:00 2001 From: debych Date: Thu, 5 Sep 2024 18:17:24 +0300 Subject: [PATCH 36/60] [Core] Optimisations --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 2 +- src/core/contracts/base/SwapCalculation.sol | 7 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +-- .../AlgebraPool.gas.spec.ts.snap | 80 +++++++++---------- .../contracts/libraries/PoolAddress.sol | 2 +- 6 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 7c99afbe0..4731231ba 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x95cae1693e5a3bc78f48679682e43b3ce024da4362d51eccdac32de2ccaba0ba; + bytes32 public constant POOL_INIT_CODE_HASH = 0x0dafac8587f1d8bb6a31389a83c5066ab6ac14bc257709e735d9c5a256839885; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 18cdfa1eb..9a3de7945 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -373,7 +373,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio if (_isPlugin()) return (0, 0); bytes4 selector; (selector, overrideFee, pluginFee) = IAlgebraPlugin(plugin).beforeSwap(msg.sender, recipient, zto, amount, limitPrice, payInAdvance, data); - if ((overrideFee + pluginFee) >= 1e6) revert incorrectPluginFee(); + // we will check that fee is less than denominator inside the swap calculation selector.shouldReturn(IAlgebraPlugin.beforeSwap.selector); } } diff --git a/src/core/contracts/base/SwapCalculation.sol b/src/core/contracts/base/SwapCalculation.sol index f00fb196d..91dab597c 100644 --- a/src/core/contracts/base/SwapCalculation.sol +++ b/src/core/contracts/base/SwapCalculation.sol @@ -62,10 +62,13 @@ abstract contract SwapCalculation is AlgebraPoolBase { if (currentPrice == 0) revert notInitialized(); if (overrideFee != 0) { cache.fee = overrideFee + pluginFee; + if (cache.fee >= 1e6) revert incorrectPluginFee(); } else { - cache.fee += pluginFee; + if (pluginFee != 0) { + cache.fee += pluginFee; + if (cache.fee >= 1e6) revert incorrectPluginFee(); + } } - if (cache.fee >= 1e6) revert incorrectPluginFee(); if (zeroToOne) { if (limitSqrtPrice >= currentPrice || limitSqrtPrice <= TickMath.MIN_SQRT_RATIO) revert invalidLimitSqrtPrice(); diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index b6088c8f7..66626d6fa 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`] = `4802891`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4802491`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4802891`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4802491`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4790694`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4790294`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4790694`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4790294`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10699`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22687`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22685`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 567165398..66b15c58b 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -58,45 +58,45 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below curr 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`] = `103487`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `103344`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103456`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block with no tick movement 1`] = `103313`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119232`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `119089`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152746`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `152603`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103625`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `103482`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152746`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `152603`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171946`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `171803`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103487`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `103344`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103451`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block with no tick movement 1`] = `103308`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `120056`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `119913`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153597`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `153454`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171946`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 several large swaps with pauses 1`] = `171803`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103325`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap after several large swaps with pauses 1`] = `103182`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103321`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact0For1 small swap with filled dataStorage 1`] = `103178`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103548`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `103405`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103496`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 first swap in block with no tick movement 1`] = `103353`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103512`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103369`; -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`] = `141761`; +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`] = `141676`; -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`] = `175909`; +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`] = `175824`; -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`] = `148849`; +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`] = `148764`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117084`; @@ -152,42 +152,42 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre 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 moves tick, no initialized crossings 1`] = `114074`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `113931`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103693`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103550`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `130056`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129913`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164281`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164138`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `114212`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `114069`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164281`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164138`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183481`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183338`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `114074`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113931`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103688`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103545`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130880`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130737`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `165132`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164989`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183481`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183338`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103562`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103419`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103558`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103415`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `114146`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `114003`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103733`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103590`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103749`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103606`; -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`] = `185944`; +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`] = `185859`; -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`] = `149086`; +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`] = `149001`; -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`] = `131986`; +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`] = `131901`; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index f991d8572..894215c53 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 = 0x95cae1693e5a3bc78f48679682e43b3ce024da4362d51eccdac32de2ccaba0ba; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x0dafac8587f1d8bb6a31389a83c5066ab6ac14bc257709e735d9c5a256839885; /// @notice The identifying key of the pool struct PoolKey { From 257e0b1ea720d98e299fe5d9e39f71e4c25cf8f6 Mon Sep 17 00:00:00 2001 From: debych Date: Thu, 5 Sep 2024 21:27:20 +0300 Subject: [PATCH 37/60] [Core] Fix test with no tick movement --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/test/AlgebraPool.gas.spec.ts | 6 +++--- src/periphery/contracts/libraries/PoolAddress.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index 4731231ba..a50d922a2 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x0dafac8587f1d8bb6a31389a83c5066ab6ac14bc257709e735d9c5a256839885; + bytes32 public constant POOL_INIT_CODE_HASH = 0x8fd873dd382d0ec6661164092e11d9b512eecf61c83826aa564a953b49710e0b; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/test/AlgebraPool.gas.spec.ts b/src/core/test/AlgebraPool.gas.spec.ts index 0f2e46f33..9863a187a 100644 --- a/src/core/test/AlgebraPool.gas.spec.ts +++ b/src/core/test/AlgebraPool.gas.spec.ts @@ -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; @@ -104,7 +104,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); }); @@ -152,7 +152,7 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { 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/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 894215c53..e8f68f5c5 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 = 0x0dafac8587f1d8bb6a31389a83c5066ab6ac14bc257709e735d9c5a256839885; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x8fd873dd382d0ec6661164092e11d9b512eecf61c83826aa564a953b49710e0b; /// @notice The identifying key of the pool struct PoolKey { From 9d92a0f042a0d5dbb643c38af64dcf3de9e94570 Mon Sep 17 00:00:00 2001 From: debych Date: Thu, 5 Sep 2024 21:40:07 +0300 Subject: [PATCH 38/60] [Core] Rework changeReserves --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/base/ReservesManager.sol | 181 +++++++++++------- .../interfaces/pool/IAlgebraPoolErrors.sol | 3 - src/core/test/AlgebraPool.spec.ts | 5 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 38 ++-- .../contracts/libraries/PoolAddress.sol | 2 +- 7 files changed, 145 insertions(+), 96 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index a50d922a2..ea7b77b6a 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x8fd873dd382d0ec6661164092e11d9b512eecf61c83826aa564a953b49710e0b; + bytes32 public constant POOL_INIT_CODE_HASH = 0x3093a65c28d1e6def42997fdea76d033dfd42b432c107e19412cf91a1eac0f91; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/base/ReservesManager.sol b/src/core/contracts/base/ReservesManager.sol index d1aa3dc11..7a65c496a 100644 --- a/src/core/contracts/base/ReservesManager.sol +++ b/src/core/contracts/base/ReservesManager.sol @@ -2,6 +2,7 @@ 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'; @@ -9,6 +10,7 @@ import '../interfaces/pool/IAlgebraPoolErrors.sol'; /// @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 @@ -67,46 +69,85 @@ 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, - address recipient, - bytes32 slot - ) internal returns (uint256, uint256, uint256, uint256) { - uint256 feePending0; - uint256 feePending1; - - assembly { - // Load the storage slot specified by the slot argument - let sl := sload(slot) - - // 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) { - (uint256 feeSent0, uint256 feeSent1) = _transferFees(feePending0, feePending1, recipient); + bytes32 receiverSlot, + bytes32 feePendingSlot + ) internal returns (uint104, uint104, uint256, uint256) { + if (fee0 | fee1 != 0) { + uint256 feePending0; + uint256 feePending1; assembly { - sstore(slot, 0) + // 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); } - return (0, 0, feeSent0, feeSent1); } else { - return (feePending0, feePending1, 0, 0); + 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) { + function _transferFees(uint256 feePending0, uint256 feePending1, address feesRecipient) private returns (uint256, uint256) { uint256 feeSent0; uint256 feeSent1; @@ -137,49 +178,57 @@ abstract contract ReservesManager is AlgebraPoolBase { uint256 pluginFee1 ) internal { if (communityFee0 > 0 || communityFee1 > 0 || pluginFee0 > 0 || pluginFee1 > 0) { - bytes32 slot; + bytes32 feePendingSlot; + bytes32 feeRecipientSlot; uint32 lastTimestamp = lastFeeTransferTimestamp; - uint32 currentTimestamp = _blockTimestamp(); bool feeSent; - if (communityFee0 | communityFee1 != 0) { - assembly { - slot := communityFeePending0.slot - } - (uint256 feePending0, uint256 feePending1, uint256 feeSent0, uint256 feeSent1) = _accrueAndTransferFees(communityFee0, communityFee1, lastTimestamp, communityVault, slot); - if (feeSent0 | feeSent1 != 0) { - (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); - feeSent = true; - } else { - (communityFeePending0, communityFeePending1) = (uint104(feePending0), uint104(feePending1)); - } + 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); } - if (pluginFee0 | pluginFee1 != 0) { - assembly { - slot := pluginFeePending0.slot - } - - (uint256 feePending0, uint256 feePending1, uint256 feeSent0, uint256 feeSent1) = _accrueAndTransferFees(pluginFee0, pluginFee1, lastTimestamp, plugin, slot); - if (feeSent0 | feeSent1 != 0) { - (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); - feeSent = true; - - if (IAlgebraPlugin(plugin).handlePluginFee(feeSent0, feeSent1) != IAlgebraPlugin.handlePluginFee.selector) revert IAlgebraPoolErrors.invalidPluginResponce(); - } else { - (pluginFeePending0, pluginFeePending1) = (uint104(feePending0), uint104(feePending1)); - } - } else if (feeSent) { - (uint256 feeSent0, uint256 feeSent1) = _transferFees(pluginFeePending0, pluginFeePending1, plugin); - if (feeSent0 | feeSent1 != 0) { - (pluginFeePending0, pluginFeePending1) = (0, 0); - (deltaR0, deltaR1) = (deltaR0 - feeSent0.toInt256(), deltaR1 - feeSent1.toInt256()); - - if (IAlgebraPlugin(plugin).handlePluginFee(feeSent0, feeSent1) != IAlgebraPlugin.handlePluginFee.selector) revert IAlgebraPoolErrors.invalidPluginResponce(); - } + 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 = currentTimestamp; + if (feeSent) lastFeeTransferTimestamp = _blockTimestamp(); } if (deltaR0 | deltaR1 == 0) return; diff --git a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol index d91bf4d18..f3a885824 100644 --- a/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol +++ b/src/core/contracts/interfaces/pool/IAlgebraPoolErrors.sol @@ -28,9 +28,6 @@ interface IAlgebraPoolErrors { /// @notice Emitted if plugin fee param greater than fee/override fee error incorrectPluginFee(); - /// @notice Emitted if a plugin returns invalid selector after pluginFeeHandle call - error invalidPluginResponce(); - /// @notice Emitted if the pool received fewer tokens than it should have error insufficientInputAmount(); diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 31f0f1925..a950e68d1 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2460,7 +2460,10 @@ describe('AlgebraPool', () => { it('swap fails if plugin return incorrect selector', async () => { await poolPlugin.disablePluginFeeHandle(); await poolPlugin.setPluginFees(5000, 4000); - await expect(swapExact0For1(expandTo18Decimals(1), wallet.address)).to.be.revertedWithCustomError(pool, 'invalidPluginResponce') ; + 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 () => { diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 66626d6fa..5facdf49b 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`] = `4802491`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4745061`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4802491`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4745061`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4790294`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4732864`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4790294`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4732864`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10699`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22685`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22399`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 66b15c58b..625afec53 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -92,11 +92,11 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103369`; -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`] = `141676`; +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`] = `142275`; -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`] = `175824`; +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`] = `176423`; -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`] = `148764`; +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`] = `148985`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117084`; @@ -152,42 +152,42 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre 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 moves tick, no initialized crossings 1`] = `113931`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `112143`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `103550`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `112112`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `129913`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `128125`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `164138`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `162350`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `114069`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `112281`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `164138`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `162350`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `183338`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `181550`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `113931`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `112143`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103545`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `130737`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `128949`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `164989`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `163201`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `183338`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `181550`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103419`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap with filled dataStorage 1`] = `103415`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `114003`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `112215`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `103590`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `112163`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103606`; -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`] = `185859`; +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`] = `185790`; -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`] = `149001`; +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`] = `152690`; -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`] = `131901`; +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`] = `132122`; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index e8f68f5c5..6696fa65e 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 = 0x8fd873dd382d0ec6661164092e11d9b512eecf61c83826aa564a953b49710e0b; + bytes32 internal constant POOL_INIT_CODE_HASH = 0x3093a65c28d1e6def42997fdea76d033dfd42b432c107e19412cf91a1eac0f91; /// @notice The identifying key of the pool struct PoolKey { From 17c5046a16fc3ae5fef86126b4d9d88e4461eae5 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 5 Sep 2024 22:47:06 +0300 Subject: [PATCH 39/60] [Core] fix plugin fee snapshots --- src/core/test/AlgebraPool.gas.spec.ts | 18 ++++++++++++------ .../__snapshots__/AlgebraPool.gas.spec.ts.snap | 8 ++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/core/test/AlgebraPool.gas.spec.ts b/src/core/test/AlgebraPool.gas.spec.ts index 9863a187a..c9d1135f6 100644 --- a/src/core/test/AlgebraPool.gas.spec.ts +++ b/src/core/test/AlgebraPool.gas.spec.ts @@ -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; @@ -123,31 +124,36 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { }) describe('#swapExact1For0 with plugin fee on', () => { - beforeEach('load the fixture', async () => { const MockPoolPluginFactory = await ethers.getContractFactory('MockPoolPlugin'); - let poolPlugin = (await MockPoolPluginFactory.deploy(await pool.getAddress())) as any as 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); }); it('first swap in block with no tick movement, without transfer', async () => { - await swapExact1For0(1000, wallet.address) + await swapExact1For0(10000, wallet.address) await pool.advanceTime(1) - await snapshotGasCost(swapExact1For0(1000, wallet.address)); + 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 swapExact1For0(1000, wallet.address); 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', () => { diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 625afec53..71d3fbe68 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -92,7 +92,7 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103369`; -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`] = `142275`; +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`] = `176475`; 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`] = `176423`; @@ -186,8 +186,8 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 f exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103606`; -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`] = `185790`; +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`] = `187790`; -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`] = `152690`; +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`] = `187738`; -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`] = `132122`; +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`] = `135590`; From fb345424f27a84ff24e14350f6b9e5303ad756e5 Mon Sep 17 00:00:00 2001 From: debych Date: Fri, 6 Sep 2024 21:02:49 +0300 Subject: [PATCH 40/60] [Core] Reset pending fees in plugin fee gas tests --- src/core/test/AlgebraPool.gas.spec.ts | 7 +++++++ .../test/__snapshots__/AlgebraPool.gas.spec.ts.snap | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/core/test/AlgebraPool.gas.spec.ts b/src/core/test/AlgebraPool.gas.spec.ts index c9d1135f6..ce2e21668 100644 --- a/src/core/test/AlgebraPool.gas.spec.ts +++ b/src/core/test/AlgebraPool.gas.spec.ts @@ -130,6 +130,13 @@ describe('AlgebraPool gas tests [ @skip-on-coverage ]', () => { 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 () => { diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 71d3fbe68..6c95b0ec6 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -92,11 +92,11 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 second swap in block with no tick movement 1`] = `103369`; -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`] = `176475`; +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`] = `157375`; -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`] = `176423`; +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`] = `157323`; -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`] = `148985`; +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`] = `131885`; exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117084`; @@ -186,8 +186,8 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 f exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103606`; -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`] = `187790`; +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`] = `168690`; -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`] = `187738`; +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`] = `168638`; 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`] = `135590`; From a9171521d0ff7be384ad27f9125c70546a721aeb Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Tue, 10 Sep 2024 10:58:32 +0300 Subject: [PATCH 41/60] [Plugin] add tests base plugin v2 --- .../test/MockTimeAlgebraBasePluginV2.sol | 70 +++ .../contracts/test/MockTimeDSFactoryV2.sol | 73 +++ src/plugin/test/AlgebraBasePluginV2.spec.ts | 541 ++++++++++++++++++ .../AlgebraBasePluginV2.spec.ts.snap | 123 ++++ src/plugin/test/shared/fixtures.ts | 33 +- 5 files changed, 836 insertions(+), 4 deletions(-) create mode 100644 src/plugin/contracts/test/MockTimeAlgebraBasePluginV2.sol create mode 100644 src/plugin/contracts/test/MockTimeDSFactoryV2.sol create mode 100644 src/plugin/test/AlgebraBasePluginV2.spec.ts create mode 100644 src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap 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/test/AlgebraBasePluginV2.spec.ts b/src/plugin/test/AlgebraBasePluginV2.spec.ts new file mode 100644 index 000000000..2b30f8bbd --- /dev/null +++ b/src/plugin/test/AlgebraBasePluginV2.spec.ts @@ -0,0 +1,541 @@ +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 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); + 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('#FarmingPlugin', () => { + describe('virtual pool tests', () => { + let virtualPoolMock: MockTimeVirtualPool; + + beforeEach('deploy virtualPoolMock', async () => { + await mockPluginFactory.setFarmingAddress(wallet); + const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); + virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; + }); + + it('set incentive works', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); + }); + + it('can detach incentive', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await plugin.setIncentive(ZeroAddress); + expect(await plugin.incentive()).to.be.eq(ZeroAddress); + }); + + it('can detach incentive even if no more has rights to connect plugins', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await mockPluginFactory.setFarmingAddress(other); + await plugin.setIncentive(ZeroAddress); + expect(await plugin.incentive()).to.be.eq(ZeroAddress); + }); + + it('cannot attach incentive even if no more has rights to connect plugins', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await mockPluginFactory.setFarmingAddress(other); + await expect(plugin.setIncentive(other)).to.be.revertedWith('Not allowed to set incentive'); + }); + + it('new farming can detach old incentive', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await mockPluginFactory.setFarmingAddress(other); + await plugin.connect(other).setIncentive(ZeroAddress); + expect(await plugin.incentive()).to.be.eq(ZeroAddress); + }); + + it('cannot detach incentive if nothing connected', async () => { + await mockPool.setPlugin(plugin); + await expect(plugin.setIncentive(ZeroAddress)).to.be.revertedWith('Already active'); + expect(await plugin.incentive()).to.be.eq(ZeroAddress); + }); + + it('cannot set same incentive twice', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Already active'); + }); + + it('cannot set incentive if has active', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await expect(plugin.setIncentive(wallet.address)).to.be.revertedWith('Has active incentive'); + }); + + it('can detach incentive if not connected to pool', async () => { + const defaultConfig = await plugin.defaultPluginConfig(); + await mockPool.setPlugin(plugin); + await mockPool.setPluginConfig(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); + await plugin.setIncentive(virtualPoolMock); + expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); + await mockPool.setPlugin(ZeroAddress); + await plugin.setIncentive(ZeroAddress); + expect(await plugin.incentive()).to.be.eq(ZeroAddress); + }); + + it('can set incentive if afterSwap hook is active', async () => { + const defaultConfig = await plugin.defaultPluginConfig(); + await mockPool.setPlugin(plugin); + await mockPool.setPluginConfig(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); + await plugin.setIncentive(virtualPoolMock); + expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); + expect((await mockPool.globalState()).pluginConfig).to.be.eq(BigInt(PLUGIN_FLAGS.AFTER_SWAP_FLAG) | defaultConfig); + }); + + it('set incentive works only for PluginFactory.farmingAddress', async () => { + await mockPluginFactory.setFarmingAddress(ZeroAddress); + await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Not allowed to set incentive'); + }); + + it('incentive can not be attached if plugin is not attached', async () => { + await expect(plugin.setIncentive(virtualPoolMock)).to.be.revertedWith('Plugin not attached'); + }); + + it('incentive attached before initialization', async () => { + await mockPool.setPlugin(plugin); + + await plugin.setIncentive(virtualPoolMock); + await mockPool.initialize(encodePriceSqrt(1, 1)); + await mockPool.mint(wallet.address, wallet.address, -120, 120, 1, '0x'); + await mockPool.mint(wallet.address, wallet.address, minTick, maxTick, 1, '0x'); + + await mockPool.swapToTick(-130); + + expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); + expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; + + const tick = (await mockPool.globalState()).tick; + expect(await virtualPoolMock.currentTick()).to.be.eq(tick); + expect(await virtualPoolMock.timestamp()).to.be.gt(0); + }); + + it('incentive attached after initialization', async () => { + await mockPool.setPlugin(plugin); + await mockPool.initialize(encodePriceSqrt(1, 1)); + await plugin.setIncentive(virtualPoolMock); + + await mockPool.mint(wallet.address, wallet.address, -120, 120, 1, '0x'); + await mockPool.mint(wallet.address, wallet.address, minTick, maxTick, 1, '0x'); + + await mockPool.swapToTick(-130); + + expect(await plugin.incentive()).to.be.eq(await virtualPoolMock.getAddress()); + expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; + + const tick = (await mockPool.globalState()).tick; + expect(await virtualPoolMock.currentTick()).to.be.eq(tick); + expect(await virtualPoolMock.timestamp()).to.be.gt(0); + }); + + it.skip('swap with finished incentive', async () => { + /*await virtualPoolMock.setIsExist(false); + await mockPool.setIncentive(virtualPoolMock.address); + await mockPool.initialize(encodePriceSqrt(1, 1)); + await mint(wallet.address, -120, 120, 1); + await mint(wallet.address, minTick, maxTick, 1); + expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); + + await swapToLowerPrice(encodePriceSqrt(1, 2), wallet.address); + + expect(await mockPool.activeIncentive()).to.be.eq(ethers.constants.AddressZero); + expect(await virtualPoolMock.currentTick()).to.be.eq(0); + expect(await virtualPoolMock.timestamp()).to.be.eq(0); + */ + }); + + it.skip('swap with not started yet incentive', async () => { + /* + await virtualPoolMock.setIsStarted(false); + await mockPool.setIncentive(virtualPoolMock.address); + await mockPool.initialize(encodePriceSqrt(1, 1)); + await mint(wallet.address, -120, 120, 1); + await mint(wallet.address, minTick, maxTick, 1); + expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); + + await swapToLowerPrice(encodePriceSqrt(1, 2), wallet.address); + + const tick = (await mockPool.globalState()).tick; + expect(await mockPool.activeIncentive()).to.be.eq(virtualPoolMock.address); + expect(await virtualPoolMock.currentTick()).to.be.eq(tick); + expect(await virtualPoolMock.timestamp()).to.be.eq(0); + */ + }); + }); + + describe('#isIncentiveConnected', () => { + let virtualPoolMock: MockTimeVirtualPool; + + beforeEach('deploy virtualPoolMock', async () => { + await mockPluginFactory.setFarmingAddress(wallet); + const virtualPoolMockFactory = await ethers.getContractFactory('MockTimeVirtualPool'); + virtualPoolMock = (await virtualPoolMockFactory.deploy()) as any as MockTimeVirtualPool; + }); + + it('true with active incentive', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.true; + }); + + it('false with invalid address', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + expect(await plugin.isIncentiveConnected(wallet.address)).to.be.false; + }); + + it('false if plugin detached', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await mockPool.setPlugin(ZeroAddress); + expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.false; + }); + + it('false if hook deactivated', async () => { + await mockPool.setPlugin(plugin); + await plugin.setIncentive(virtualPoolMock); + await mockPool.setPluginConfig(0); + expect(await plugin.isIncentiveConnected(virtualPoolMock)).to.be.false; + }); + }); + + describe('#Incentive', () => { + it('incentive is not detached after swap', async () => { + await mockPool.setPlugin(plugin); + await initializeAtZeroTick(mockPool); + await mockPluginFactory.setFarmingAddress(wallet.address); + + const vpStubFactory = await ethers.getContractFactory('MockTimeVirtualPool'); + let vpStub = (await vpStubFactory.deploy()) as any as MockTimeVirtualPool; + + await plugin.setIncentive(vpStub); + const initLiquidityAmount = 10000000000n; + await mockPool.mint(wallet.address, wallet.address, -120, 120, initLiquidityAmount, '0x'); + await mockPool.mint(wallet.address, wallet.address, -1200, 1200, initLiquidityAmount, '0x'); + await mockPool.swapToTick(-200); + + expect(await plugin.incentive()).to.be.eq(await vpStub.getAddress()); + }); + }); + }); + +}); diff --git a/src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap new file mode 100644 index 000000000..63e73906c --- /dev/null +++ b/src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap @@ -0,0 +1,123 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge spike after day 1`] = ` +Array [ + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 123 ", + "Fee: 124 ", + "Fee: 124 ", + "Fee: 124 ", + "Fee: 124 ", + "Fee: 100 ", +] +`; + +exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge spike after initialization 1`] = ` +Array [ + "Fee: 3714 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 2965 ", + "Fee: 2602 ", + "Fee: 1733 ", + "Fee: 995 ", + "Fee: 607 ", + "Fee: 411 ", + "Fee: 309 ", + "Fee: 250 ", + "Fee: 215 ", + "Fee: 191 ", + "Fee: 174 ", + "Fee: 163 ", + "Fee: 154 ", + "Fee: 147 ", + "Fee: 142 ", + "Fee: 139 ", + "Fee: 135 ", + "Fee: 132 ", + "Fee: 130 ", + "Fee: 128 ", + "Fee: 126 ", + "Fee: 100 ", +] +`; + +exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge step after day 1`] = ` +Array [ + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 3000 ", + "Fee: 100 ", +] +`; + +exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge step after initialization 1`] = ` +Array [ + "Fee: 15000 ", + "Fee: 15000 ", + "Fee: 15000 ", + "Fee: 15000 ", + "Fee: 14311 ", + "Fee: 11402 ", + "Fee: 7651 ", + "Fee: 5386 ", + "Fee: 4312 ", + "Fee: 3795 ", + "Fee: 3525 ", + "Fee: 3371 ", + "Fee: 3277 ", + "Fee: 3216 ", + "Fee: 3174 ", + "Fee: 3144 ", + "Fee: 3123 ", + "Fee: 3106 ", + "Fee: 3093 ", + "Fee: 3083 ", + "Fee: 3075 ", + "Fee: 3068 ", + "Fee: 3062 ", + "Fee: 3058 ", + "Fee: 3037 ", +] +`; + +exports[`AlgebraBasePluginV2 AlgebraBasePluginV1 external methods #changeFeeConfiguration feeConfig getter gas cost [ @skip-on-coverage ] 1`] = `23708`; diff --git a/src/plugin/test/shared/fixtures.ts b/src/plugin/test/shared/fixtures.ts index a9f53da9a..b6614ef1f 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,28 @@ export const pluginFactoryFixture: Fixture = async functio 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 From 6b0f859fbe480252452a853ce1e71a34fd05e73f Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 27 Sep 2024 11:37:14 +0300 Subject: [PATCH 42/60] [Plugin] sliding fee fixes --- src/plugin/contracts/AlgebraBasePluginV2.sol | 27 +-- src/plugin/contracts/BasePluginV2Factory.sol | 79 +++++++ .../interfaces/IBasePluginV2Factory.sol | 46 ++++ .../interfaces/plugins/ISlidingFeePlugin.sol | 11 + .../contracts/plugins/SlidingFeePlugin.sol | 150 +++++++------ src/plugin/contracts/test/SlidingFeeTest.sol | 29 +++ src/plugin/test/SlidingFee.spec.ts | 211 ++++++++++++++++++ .../__snapshots__/SlidingFee.spec.ts.snap | 7 + 8 files changed, 476 insertions(+), 84 deletions(-) create mode 100644 src/plugin/contracts/BasePluginV2Factory.sol create mode 100644 src/plugin/contracts/interfaces/IBasePluginV2Factory.sol create mode 100644 src/plugin/contracts/interfaces/plugins/ISlidingFeePlugin.sol create mode 100644 src/plugin/contracts/test/SlidingFeeTest.sol create mode 100644 src/plugin/test/SlidingFee.spec.ts create mode 100644 src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap diff --git a/src/plugin/contracts/AlgebraBasePluginV2.sol b/src/plugin/contracts/AlgebraBasePluginV2.sol index ae1a9bbdf..06d5c3556 100644 --- a/src/plugin/contracts/AlgebraBasePluginV2.sol +++ b/src/plugin/contracts/AlgebraBasePluginV2.sol @@ -11,15 +11,14 @@ import './plugins/VolatilityOraclePlugin.sol'; /// @title Algebra Integral 1.1 default plugin /// @notice This contract stores timepoints and calculates adaptive fee and statistical averages -contract AlgebraBasePluginV1 is SlidingFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin { +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); + 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) { - - } + constructor(address _pool, address _factory, address _pluginFactory) BasePlugin(_pool, _factory, _pluginFactory) {} // ###### HOOKS ###### @@ -47,15 +46,12 @@ contract AlgebraBasePluginV1 is SlidingFeePlugin, FarmingProxyPlugin, Volatility } function beforeSwap(address, address, bool zeroToOne, int256, uint160, bool, bytes calldata) external override onlyPool returns (bytes4, uint24, uint24) { - ( , int24 currentTick, uint16 fee, ) = _getPoolState(); + ( , int24 currentTick, , ) = _getPoolState(); int24 lastTick = _getLastTick(); - uint16 newFee = _getFeeAndUpdateFactors(zeroToOne, currentTick, lastTick, fee); - if (newFee != fee) { - IAlgebraPool(pool).setFee(newFee); - } + uint16 newFee = _getFeeAndUpdateFactors(zeroToOne, currentTick, lastTick); _writeTimepoint(); - return (IAlgebraPlugin.beforeSwap.selector, 0, 0); + return (IAlgebraPlugin.beforeSwap.selector, newFee, 0); } function afterSwap(address, address, bool zeroToOne, int256, uint160, int256, int256, bytes calldata) external override onlyPool returns (bytes4) { @@ -75,8 +71,7 @@ contract AlgebraBasePluginV1 is SlidingFeePlugin, FarmingProxyPlugin, Volatility return IAlgebraPlugin.afterFlash.selector; } - function getCurrentFee() external view returns(uint16 fee) { - ( , , fee, ) = _getPoolState(); - } - -} + function getCurrentFee() external view returns (uint16 fee) { + (, , fee, ) = _getPoolState(); + } +} \ No newline at end of file diff --git a/src/plugin/contracts/BasePluginV2Factory.sol b/src/plugin/contracts/BasePluginV2Factory.sol new file mode 100644 index 000000000..adcf07819 --- /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.1 default plugin factory +/// @notice This contract creates Algebra default 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); + } +} \ No newline at end of file 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/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/plugins/SlidingFeePlugin.sol b/src/plugin/contracts/plugins/SlidingFeePlugin.sol index a57134bff..9585adfd9 100644 --- a/src/plugin/contracts/plugins/SlidingFeePlugin.sol +++ b/src/plugin/contracts/plugins/SlidingFeePlugin.sol @@ -1,92 +1,106 @@ // 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 { - struct FeeFactors { - uint128 zeroToOneFeeFactor; - uint128 oneToZeroFeeFactor; - } +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; - uint64 internal constant FEE_FACTOR_SHIFT = 96; + constructor() { + FeeFactors memory feeFactors = FeeFactors(uint128(1 << FEE_FACTOR_SHIFT), uint128(1 << FEE_FACTOR_SHIFT)); - FeeFactors public s_feeFactors; + s_feeFactors = feeFactors; + } - uint256 public s_priceChangeFactor = 1; + function _getFeeAndUpdateFactors(bool zeroToOne, int24 currenTick, int24 lastTick) internal returns (uint16) { + FeeFactors memory currentFeeFactors; - event PriceChangeFactor(uint256 priceChangeFactor); + uint16 priceChangeFactor = s_priceChangeFactor; + uint16 baseFee = s_baseFee; - constructor() { - FeeFactors memory feeFactors = FeeFactors( - uint128(1 << FEE_FACTOR_SHIFT), - uint128(1 << FEE_FACTOR_SHIFT) - ); + if (currenTick != lastTick) { + currentFeeFactors = _calculateFeeFactors(currenTick, lastTick, priceChangeFactor); - s_feeFactors = feeFactors; + s_feeFactors = currentFeeFactors; + } else { + currentFeeFactors = s_feeFactors; } - function _getFeeAndUpdateFactors( - bool zeroToOne, - int24 currentTick, - int24 lastTick, - uint16 poolFee - ) internal returns (uint16) { - FeeFactors memory currentFeeFactors; + uint256 adjustedFee = zeroToOne + ? (uint256(baseFee) * currentFeeFactors.zeroToOneFeeFactor) >> FEE_FACTOR_SHIFT + : (uint256(baseFee) * currentFeeFactors.oneToZeroFeeFactor) >> FEE_FACTOR_SHIFT; - currentFeeFactors = _calculateFeeFactors(currentTick, lastTick); + if (adjustedFee > type(uint16).max) adjustedFee = type(uint16).max; + return uint16(adjustedFee); + } - s_feeFactors = currentFeeFactors; + function setPriceChangeFactor(uint16 newPriceChangeFactor) external override { + require(IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); - uint16 adjustedFee = zeroToOne ? - uint16((poolFee * currentFeeFactors.zeroToOneFeeFactor) >> FEE_FACTOR_SHIFT) : - uint16((poolFee * currentFeeFactors.oneToZeroFeeFactor) >> FEE_FACTOR_SHIFT); + s_priceChangeFactor = newPriceChangeFactor; - return adjustedFee; - } + emit PriceChangeFactor(newPriceChangeFactor); + } + + function setBaseFee(uint16 newBaseFee) external override { + require(msg.sender == pluginFactory || IAlgebraFactory(factory).hasRoleOrOwner(ALGEBRA_BASE_PLUGIN_MANAGER, msg.sender)); - function _calculateFeeFactors( - int24 currentTick, - int24 lastTick - ) internal view returns (FeeFactors memory feeFactors) { - // price change is positive after zeroToOne prevalence - int256 priceChangeRatio = int256(uint256(TickMath.getSqrtRatioAtTick(currentTick - lastTick))) - int256(1 << FEE_FACTOR_SHIFT); // (currentPrice - lastPrice) / lastPrice - int128 feeFactorImpact = int128(priceChangeRatio * int256(s_priceChangeFactor)); - - feeFactors = s_feeFactors; - - // if there were zeroToOne prevalence in the last price change, - // in result price has increased - // we need to increase zeroToOneFeeFactor - // and vice versa - int128 newZeroToOneFeeFactor = int128(feeFactors.zeroToOneFeeFactor) + feeFactorImpact; - - if ((int128(-2) << FEE_FACTOR_SHIFT) < newZeroToOneFeeFactor && newZeroToOneFeeFactor < int128(uint128(2) << FEE_FACTOR_SHIFT)) { - feeFactors = FeeFactors( - uint128(newZeroToOneFeeFactor), - uint128(int128(feeFactors.oneToZeroFeeFactor) - 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( - uint128(2 << FEE_FACTOR_SHIFT), - 0 - ); - } 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( - 0, - uint128(2 << FEE_FACTOR_SHIFT) - ); - } + 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); } + } } \ No newline at end of file diff --git a/src/plugin/contracts/test/SlidingFeeTest.sol b/src/plugin/contracts/test/SlidingFeeTest.sol new file mode 100644 index 000000000..9d47fa8ca --- /dev/null +++ b/src/plugin/contracts/test/SlidingFeeTest.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.20; + +import '../plugins/SlidingFeePlugin.sol'; + +contract SlidingFeeTest is SlidingFeePlugin { + 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); + } + + 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; + } +} \ No newline at end of file diff --git a/src/plugin/test/SlidingFee.spec.ts b/src/plugin/test/SlidingFee.spec.ts new file mode 100644 index 000000000..57e5ed6b2 --- /dev/null +++ b/src/plugin/test/SlidingFee.spec.ts @@ -0,0 +1,211 @@ +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('#getSlidingFee', () => { + 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('#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__/SlidingFee.spec.ts.snap b/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap new file mode 100644 index 000000000..6efd0b687 --- /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`] = `26753`; + +exports[`SlidingFee #getFee gas cost [ @skip-on-coverage ] gas cost of tick decrease 1`] = `31852`; + +exports[`SlidingFee #getFee gas cost [ @skip-on-coverage ] gas cost of tick increase 1`] = `31738`; From 824369410918fffe7dc541d942278f416ac29387 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 18 Sep 2024 16:12:19 +0300 Subject: [PATCH 43/60] [Periphery] returns lists of amountIn/Out in exactInput/Output --- .../contracts/interfaces/IQuoterV2.sol | 16 +-- src/periphery/contracts/lens/QuoterV2.sol | 40 +++--- src/periphery/test/QuoterV2.spec.ts | 129 +++++++++--------- .../test/__snapshots__/QuoterV2.spec.ts.snap | 28 ++-- 4 files changed, 105 insertions(+), 108 deletions(-) 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/QuoterV2.sol b/src/periphery/contracts/lens/QuoterV2.sol index 8edf893ae..492c02de2 100644 --- a/src/periphery/contracts/lens/QuoterV2.sol +++ b/src/periphery/contracts/lens/QuoterV2.sol @@ -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/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/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap index 77df00a47..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`] = `156869`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `157987`; -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156805`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `157893`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `248736`; +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`] = `106435`; +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`] = `91088`; +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`] = `126531`; +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`] = `156770`; +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`] = `126540`; +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`] = `126874`; +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`] = `157387`; +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`] = `157391`; +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`] = `91985`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `92876`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92069`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `92935`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92086`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `92952`; From e099606ccca450fe74233cd07e5cdf4b6530791d Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 18 Sep 2024 16:30:07 +0300 Subject: [PATCH 44/60] [Periphery] add plugin init data to createAndInitializePoolIfNecessary --- .../contracts/base/PoolInitializer.sol | 5 +- .../contracts/interfaces/IPoolInitializer.sol | 4 +- .../test/NonfungiblePositionManager.spec.ts | 70 +++++++++++-------- .../NonfungiblePositionManager.spec.ts.snap | 32 ++++----- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/periphery/contracts/base/PoolInitializer.sol b/src/periphery/contracts/base/PoolInitializer.sol index 0c59de7b8..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/test/NonfungiblePositionManager.spec.ts b/src/periphery/test/NonfungiblePositionManager.spec.ts index eb7fe87a7..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 () => { @@ -105,7 +105,7 @@ describe('NonfungiblePositionManager', () => { 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 () => { @@ -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/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index ff2e52281..447467e09 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`] = `125983`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `126407`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `122321`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `122745`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `122512`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `122936`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5277310`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5231043`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171239`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171665`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `176207`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `176589`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `182683`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `184149`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `635168`; +exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `636773`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `649531`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with non-zero refund [ @skip-on-coverage ] 1`] = `651121`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `642364`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `643954`; -exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `440868`; +exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `442385`; -exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `330678`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `332156`; -exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60014`; +exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60003`; -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`] = `63869`; 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`] = `21901`; +exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `22015`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `246963`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `247432`; From 2df017183bc3d326284ba85c649ddfade9686115 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 27 Sep 2024 12:11:32 +0300 Subject: [PATCH 45/60] [Core] add pluginFee to swap and burn events --- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 37 ++++- .../interfaces/pool/IAlgebraPoolEvents.sol | 25 +++- src/core/test/AlgebraPool.spec.ts | 10 +- src/core/test/AlgebraPool.swaps.spec.ts | 4 +- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 +- .../AlgebraPool.gas.spec.ts.snap | 128 +++++++++--------- .../contracts/libraries/PoolAddress.sol | 2 +- 8 files changed, 134 insertions(+), 84 deletions(-) diff --git a/src/core/contracts/AlgebraFactory.sol b/src/core/contracts/AlgebraFactory.sol index ea7b77b6a..b54650515 100644 --- a/src/core/contracts/AlgebraFactory.sol +++ b/src/core/contracts/AlgebraFactory.sol @@ -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 = 0x3093a65c28d1e6def42997fdea76d033dfd42b432c107e19412cf91a1eac0f91; + bytes32 public constant POOL_INIT_CODE_HASH = 0xb3fc09be5eb433d99b1ec89fd8435aaf5ffea75c1879e19028aa2414a14b3c85; constructor(address _poolDeployer) { require(_poolDeployer != address(0)); diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index 9a3de7945..a64fe7625 100644 --- a/src/core/contracts/AlgebraPool.sol +++ b/src/core/contracts/AlgebraPool.sol @@ -165,7 +165,7 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } } - 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); @@ -278,7 +278,16 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio _changeReserves(amount0, amount1, 0, fees.communityFeeAmount, 0, fees.pluginFeeAmount); // reflect reserve change and pay communityFee } - _emitSwapEvent(recipient, amount0, amount1, eventParams.currentPrice, eventParams.currentLiquidity, eventParams.currentTick); + _emitSwapEvent( + recipient, + amount0, + amount1, + eventParams.currentPrice, + eventParams.currentLiquidity, + eventParams.currentTick, + overrideFee, + pluginFee + ); } _unlock(); @@ -350,15 +359,33 @@ contract AlgebraPool is AlgebraPoolBase, TickStructure, ReentrancyGuard, Positio } } - _emitSwapEvent(recipient, amount0, amount1, eventParams.currentPrice, eventParams.currentLiquidity, eventParams.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( 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/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index a950e68d1..f5a1b2cd5 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -1130,7 +1130,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 +1148,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 +1168,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)) @@ -2054,7 +2054,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 +2065,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); 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 5facdf49b..977a472ff 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`] = `4745061`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4756512`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4745061`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4756512`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4732864`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4744315`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4732864`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4744315`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10699`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22399`; +exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22456`; diff --git a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap index 6c95b0ec6..52a5071fd 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -4,29 +4,29 @@ 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`] = `117084`; +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`] = `117084`; +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`] = `110881`; +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`] = `115681`; +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`] = `126964`; +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`] = `126964`; +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`] = `115289`; +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`] = `120089`; +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`] = `126520`; +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`] = `126520`; +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`] = `111542`; +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`] = `116342`; +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`] = `52641`; @@ -58,69 +58,69 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #mint below curr 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`] = `103344`; +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`] = `103313`; +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`] = `119089`; +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`] = `152603`; +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`] = `103482`; +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`] = `152603`; +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`] = `171803`; +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`] = `103344`; +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`] = `103308`; +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`] = `119913`; +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`] = `153454`; +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`] = `171803`; +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`] = `103182`; +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`] = `103178`; +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`] = `103405`; +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`] = `103353`; +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`] = `103369`; +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 off #swapExact1For0 with plugin fee on first swap in block moves tick, no initialized crossings, with transfer 1`] = `157375`; +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`] = `157999`; -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`] = `157323`; +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`] = `157947`; -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`] = `131885`; +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`] = `132509`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #burn above current price burn entire position after some time passes 1`] = `117084`; +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 above current price burn when only position using ticks 1`] = `117084`; +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 above current price entire position burn but other positions are using the ticks 1`] = `110881`; +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 above current price partial position burn 1`] = `115681`; +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 burn entire position after some time passes 1`] = `126964`; +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 around current price burn when only position using ticks 1`] = `126964`; +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 around current price entire position burn but other positions are using the ticks 1`] = `115289`; +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 around current price partial position burn 1`] = `120089`; +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 burn entire position after some time passes 1`] = `126520`; +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 #burn below current price burn when only position using ticks 1`] = `126520`; +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 #burn below current price entire position burn but other positions are using the ticks 1`] = `111542`; +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 #burn below current price partial position burn 1`] = `116342`; +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 #collect close to worst case 1`] = `52641`; @@ -152,42 +152,42 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre 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 moves tick, no initialized crossings 1`] = `112143`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block moves tick, no initialized crossings 1`] = `112770`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `112112`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block with no tick movement 1`] = `112739`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `128125`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing a single initialized tick 1`] = `128752`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `162350`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap crossing several initialized ticks 1`] = `162977`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `112281`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 first swap in block, large swap, no initialized crossings 1`] = `112908`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `162350`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks after some time passes 1`] = `162977`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `181550`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 large swap crossing several initialized ticks second time after some time passes 1`] = `182177`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `112143`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block moves tick, no initialized crossings 1`] = `112770`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block with no tick movement 1`] = `103545`; +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 second swap in block, large swap crossing a single initialized tick 1`] = `128949`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129576`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `163201`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing several initialized ticks 1`] = `163828`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `181550`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 several large swaps with pauses 1`] = `182177`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 small swap after several large swaps with pauses 1`] = `103419`; +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 #swapExact0For1 small swap with filled dataStorage 1`] = `103415`; +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`] = `112215`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block moves tick, no initialized crossings 1`] = `112842`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `112163`; +exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 first swap in block with no tick movement 1`] = `112790`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact1For0 second swap in block with no tick movement 1`] = `103606`; +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`] = `168690`; +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`] = `169095`; -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`] = `168638`; +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`] = `169043`; -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`] = `135590`; +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`] = `136208`; diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 6696fa65e..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 = 0x3093a65c28d1e6def42997fdea76d033dfd42b432c107e19412cf91a1eac0f91; + bytes32 internal constant POOL_INIT_CODE_HASH = 0xb3fc09be5eb433d99b1ec89fd8435aaf5ffea75c1879e19028aa2414a14b3c85; /// @notice The identifying key of the pool struct PoolKey { From cefcb293feec15e958d537a720189996baeb2fbc Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 27 Sep 2024 12:40:43 +0300 Subject: [PATCH 46/60] [Common] some fixes --- src/farming/contracts/FarmingCenter.sol | 2 +- src/plugin/contracts/base/BasePlugin.sol | 37 +++++++++++++------ .../contracts/interfaces/IBasePlugin.sol | 4 -- .../interfaces/plugins/IFarmingPlugin.sol | 4 ++ .../contracts/plugins/FarmingProxyPlugin.sol | 4 +- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/farming/contracts/FarmingCenter.sol b/src/farming/contracts/FarmingCenter.sol index b704a964e..e5c5d9ea3 100644 --- a/src/farming/contracts/FarmingCenter.sol +++ b/src/farming/contracts/FarmingCenter.sol @@ -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/plugin/contracts/base/BasePlugin.sol b/src/plugin/contracts/base/BasePlugin.sol index 93850a00c..3713cf7b1 100644 --- a/src/plugin/contracts/base/BasePlugin.sol +++ b/src/plugin/contracts/base/BasePlugin.sol @@ -21,7 +21,7 @@ abstract contract BasePlugin is IBasePlugin, Timestamp { uint8 private constant defaultPluginConfig = 0; - address public immutable override pool; + address public immutable pool; address internal immutable factory; address internal immutable pluginFactory; @@ -59,40 +59,56 @@ abstract contract BasePlugin is IBasePlugin, Timestamp { // ###### HOOKS ###### - function beforeInitialize(address, uint160) external override virtual onlyPool returns (bytes4) { + function beforeInitialize(address, uint160) external virtual override onlyPool returns (bytes4) { return IAlgebraPlugin.beforeInitialize.selector; } - function afterInitialize(address, uint160, int24) external override virtual onlyPool returns (bytes4) { + 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 override virtual onlyPool returns (bytes4, uint24) { + 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 override virtual onlyPool returns (bytes4) { + 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 override virtual onlyPool returns (bytes4, uint24, uint24) { + 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 override virtual onlyPool returns (bytes4) { + 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 override virtual onlyPool returns (bytes4) { + 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 override virtual onlyPool returns (bytes4) { + 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); @@ -114,5 +130,4 @@ abstract contract BasePlugin is IBasePlugin, Timestamp { IAlgebraPool(pool).setPluginConfig(newPluginConfig); } } - } diff --git a/src/plugin/contracts/interfaces/IBasePlugin.sol b/src/plugin/contracts/interfaces/IBasePlugin.sol index 4730809d0..c31d3fecd 100644 --- a/src/plugin/contracts/interfaces/IBasePlugin.sol +++ b/src/plugin/contracts/interfaces/IBasePlugin.sol @@ -11,8 +11,4 @@ interface IBasePlugin is IAlgebraPlugin { /// @param amount Amount of tokens /// @param recipient Recipient address function collectPluginFee(address token, uint256 amount, address recipient) external; - - /// @notice Returns the address of the pool the plugin is created for - /// @return address of the pool - function pool() external view returns (address); } diff --git a/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol b/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol index a5e43882f..b914a99c5 100644 --- a/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol +++ b/src/plugin/contracts/interfaces/plugins/IFarmingPlugin.sol @@ -25,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/plugins/FarmingProxyPlugin.sol b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol index 4e54da36f..39bfb6d39 100644 --- a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol +++ b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol @@ -77,7 +77,9 @@ abstract contract FarmingProxyPlugin is BasePlugin, IFarmingPlugin { } else { _disablePluginFlags(defaultPluginConfig); // should not be called, reset config } - } + function getPool() external view override returns (address) { + return pool; + } } From 7c1024af68e962c2e1b0249c45f5a24dae3e105a Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 27 Sep 2024 12:42:17 +0300 Subject: [PATCH 47/60] [Common] change config and deployment script --- hardhat.base.config.ts | 5 +++++ src/farming/scripts/deploy.js | 4 ++-- src/periphery/scripts/deploy.js | 2 +- src/plugin/scripts/deploy.js | 6 +++--- src/plugin/scripts/verify.js | 4 ++-- 5 files changed, 13 insertions(+), 8 deletions(-) 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/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/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/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 ], From 5fa6215092293a2183e4ae366ba1602b997bcc00 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 12:25:23 +0300 Subject: [PATCH 48/60] [Core] tests for plugin caller --- src/core/contracts/test/MockPoolPlugin.sol | 26 ++++++++++++++++++++++ src/core/test/AlgebraPool.spec.ts | 20 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/core/contracts/test/MockPoolPlugin.sol b/src/core/contracts/test/MockPoolPlugin.sol index b3675f9aa..cf3b1052d 100644 --- a/src/core/contracts/test/MockPoolPlugin.sol +++ b/src/core/contracts/test/MockPoolPlugin.sol @@ -6,6 +6,7 @@ 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; @@ -197,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/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index f5a1b2cd5..5aa088a54 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; @@ -2898,6 +2899,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', () => { From 4d943d4c00e8c40367b806e92092bfa234d52e04 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 12:27:00 +0300 Subject: [PATCH 49/60] [Core] custom data on pool creation test --- src/core/contracts/test/MockDefaultPluginFactory.sol | 5 ++++- src/core/test/AlgebraFactory.spec.ts | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) 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/test/AlgebraFactory.spec.ts b/src/core/test/AlgebraFactory.spec.ts index 7843aafcf..1d59fe0ac 100644 --- a/src/core/test/AlgebraFactory.spec.ts +++ b/src/core/test/AlgebraFactory.spec.ts @@ -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]]); From bf756cdf25aec83fbc7d5cd8dfec2374f2825d06 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 13:10:38 +0300 Subject: [PATCH 50/60] [Core] plugin and override fee in Swap/Burn events test --- src/core/test/AlgebraPool.spec.ts | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 5aa088a54..790901256 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -2506,6 +2506,18 @@ describe('AlgebraPool', () => { 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); @@ -2529,6 +2541,30 @@ describe('AlgebraPool', () => { 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 () => { From 6ab190d652cd35f33058e2ab05cc14e227070fcd Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 17:26:20 +0300 Subject: [PATCH 51/60] [Core] update snapshots & add plugin fee transfer test --- src/core/test/AlgebraPool.spec.ts | 47 ++++++++++++++++++- .../__snapshots__/AlgebraFactory.spec.ts.snap | 10 ++-- .../AlgebraPool.gas.spec.ts.snap | 38 +++++++-------- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/core/test/AlgebraPool.spec.ts b/src/core/test/AlgebraPool.spec.ts index 790901256..0a7b4dc7b 100644 --- a/src/core/test/AlgebraPool.spec.ts +++ b/src/core/test/AlgebraPool.spec.ts @@ -1186,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)) @@ -2529,6 +2529,51 @@ describe('AlgebraPool', () => { 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); diff --git a/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap b/src/core/test/__snapshots__/AlgebraFactory.spec.ts.snap index 977a472ff..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`] = `4756512`; +exports[`AlgebraFactory #createCustomPool gas [ @skip-on-coverage ] 1`] = `4752295`; -exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4756512`; +exports[`AlgebraFactory #createCustomPool gas for second pool [ @skip-on-coverage ] 1`] = `4752295`; -exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4744315`; +exports[`AlgebraFactory #createPool gas [ @skip-on-coverage ] 1`] = `4740098`; -exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4744315`; +exports[`AlgebraFactory #createPool gas for second pool [ @skip-on-coverage ] 1`] = `4740098`; exports[`AlgebraFactory factory bytecode size [ @skip-on-coverage ] 1`] = `10699`; -exports[`AlgebraFactory pool bytecode size [ @skip-on-coverage ] 1`] = `22456`; +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 52a5071fd..c7f4f1b7e 100644 --- a/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/core/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -92,11 +92,11 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is off #swapExact1For0 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 off #swapExact1For0 with plugin fee on first swap in block moves tick, no initialized crossings, with transfer 1`] = `157999`; +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 off #swapExact1For0 with plugin fee on first swap in block with no tick movement, with transfer 1`] = `157947`; +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 off #swapExact1For0 with plugin fee on first swap in block with no tick movement, without transfer 1`] = `132509`; +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 burn entire position after some time passes 1`] = `117308`; @@ -152,42 +152,42 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #mint below curre 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 moves tick, no initialized crossings 1`] = `112770`; +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 with no tick movement 1`] = `112739`; +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 first swap in block, large swap crossing a single initialized tick 1`] = `128752`; +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 first swap in block, large swap crossing several initialized ticks 1`] = `162977`; +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 first swap in block, large swap, no initialized crossings 1`] = `112908`; +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 large swap crossing several initialized ticks after some time passes 1`] = `162977`; +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 large swap crossing several initialized ticks second time after some time passes 1`] = `182177`; +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 moves tick, no initialized crossings 1`] = `112770`; +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 second swap in block with no tick movement 1`] = `104112`; -exports[`AlgebraPool gas tests [ @skip-on-coverage ] fee is on #swapExact0For1 second swap in block, large swap crossing a single initialized tick 1`] = `129576`; +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 second swap in block, large swap crossing several initialized ticks 1`] = `163828`; +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 #swapExact0For1 several large swaps with pauses 1`] = `182177`; +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 #swapExact0For1 small swap after several large swaps with pauses 1`] = `103986`; 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`] = `112842`; +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`] = `112790`; +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`] = `169095`; +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`] = `169043`; +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`] = `136208`; +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`; From f1d78cb4e1e742051e7dfef87289d23980d27cc0 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 18:26:05 +0300 Subject: [PATCH 52/60] [Plugin] MIN sliding fee value --- src/plugin/contracts/plugins/SlidingFeePlugin.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugin/contracts/plugins/SlidingFeePlugin.sol b/src/plugin/contracts/plugins/SlidingFeePlugin.sol index 9585adfd9..a86a56a3a 100644 --- a/src/plugin/contracts/plugins/SlidingFeePlugin.sol +++ b/src/plugin/contracts/plugins/SlidingFeePlugin.sol @@ -48,7 +48,11 @@ abstract contract SlidingFeePlugin is BasePlugin, ISlidingFeePlugin { ? (uint256(baseFee) * currentFeeFactors.zeroToOneFeeFactor) >> FEE_FACTOR_SHIFT : (uint256(baseFee) * currentFeeFactors.oneToZeroFeeFactor) >> FEE_FACTOR_SHIFT; - if (adjustedFee > type(uint16).max) adjustedFee = type(uint16).max; + if (adjustedFee > type(uint16).max) { + adjustedFee = type(uint16).max; + } else if (adjustedFee == 0) { + adjustedFee = 1; + } return uint16(adjustedFee); } @@ -103,4 +107,4 @@ abstract contract SlidingFeePlugin is BasePlugin, ISlidingFeePlugin { feeFactors = FeeFactors(uint128(2 << FEE_FACTOR_SHIFT), 0); } } -} \ No newline at end of file +} From ee41e68da7438f0078dae088ae707cf3d9e3541f Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 18:52:00 +0300 Subject: [PATCH 53/60] [Periphery] update snapshots --- .../NonfungibleTokenPositionDescriptor.spec.ts | 4 ++-- src/periphery/test/PositionValue.spec.ts | 2 +- src/periphery/test/SwapRouter.spec.ts | 2 +- src/periphery/test/TickLens.spec.ts | 2 +- src/periphery/test/V3Migrator.spec.ts | 14 +++++++------- .../NonfungiblePositionManager.spec.ts.snap | 18 +++++++++--------- .../__snapshots__/PoolAddress.spec.ts.snap | 2 +- .../__snapshots__/PositionValue.spec.ts.snap | 8 ++++---- .../test/__snapshots__/TickLens.spec.ts.snap | 12 ++++++------ .../test/__snapshots__/V3Migrator.spec.ts.snap | 2 +- src/periphery/test/shared/quoter.ts | 6 +++--- 11 files changed, 36 insertions(+), 36 deletions(-) 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/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__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index 447467e09..07ca780c7 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -8,23 +8,23 @@ exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-o exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `122936`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5231043`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5234040`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171665`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `171889`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `176589`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `176869`; exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `184149`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `636773`; +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`] = `651121`; +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`] = `643954`; +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`] = `442385`; +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`] = `332156`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `332144`; exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60003`; @@ -36,4 +36,4 @@ exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @ski exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `22015`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `247432`; +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 52345af82..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`] = `"0xe5Ddfe98A0958dF35aAEac697d7E589baF0c5C79"`; +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 3fc64e25c..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`] = `51867`; +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`] = `51933`; +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`] = `57458`; +exports[`PositionValue #fees when price is within the position range gas 1`] = `60204`; exports[`PositionValue #principal gas 1`] = `26316`; -exports[`PositionValue #total gas 1`] = `60549`; +exports[`PositionValue #total gas 1`] = `63295`; diff --git a/src/periphery/test/__snapshots__/TickLens.spec.ts.snap b/src/periphery/test/__snapshots__/TickLens.spec.ts.snap index 722c60622..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`] = `31483`; +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`] = `22186`; +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`] = `53689`; +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`] = `2465710`; +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`] = `2507102`; +exports[`TickLens fully populated word getNextActiveTicks 512 ticks [ @skip-on-coverage ] 1`] = `2690142`; -exports[`TickLens fully populated word getPopulatedTicksInWord [ @skip-on-coverage ] 1`] = `92600`; +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 a93877c95..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`] = `754282`; +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, From 41ab42f26ea24bdc42618a9415cd36788f3ced45 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 18:49:48 +0300 Subject: [PATCH 54/60] [Farming] fix tests & update snapshots --- src/farming/.solcover.js | 1 + src/farming/test/AlgebraFarming.spec.ts | 8 ++++---- src/farming/test/helpers/index.ts | 4 ++-- src/farming/test/shared/fixtures.ts | 4 ++-- src/farming/test/unit/EternalFarms.spec.ts | 8 ++++---- src/farming/test/unit/FarmingCenter.spec.ts | 4 ++-- src/farming/test/unit/Multicall.spec.ts | 4 +--- .../test/unit/__snapshots__/EternalFarms.spec.ts.snap | 4 ++-- 8 files changed, 18 insertions(+), 19 deletions(-) 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/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..780b77717 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, @@ -1594,7 +1594,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); @@ -1898,7 +1898,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..58f91bb65 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, 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 7eee69436..2616188c3 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`] = `498913`; +exports[`unit/EternalFarms #enterFarming works and has gas cost [ @skip-on-coverage ] 1`] = `506125`; -exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `171767`; +exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `175750`; From 8e5967b90dfc219947f8fda021a6eb5c54817317 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Mon, 7 Oct 2024 21:31:25 +0300 Subject: [PATCH 55/60] [Plugin] add some tests for plugin fee && update snapshots --- src/plugin/contracts/AlgebraBasePluginV2.sol | 18 ++- src/plugin/contracts/test/SlidingFeeTest.sol | 4 +- src/plugin/test/AlgebraBasePluginV1.spec.ts | 8 -- src/plugin/test/AlgebraBasePluginV2.spec.ts | 52 ++++++-- src/plugin/test/BasePluginV2Factory.spec.ts | 125 ++++++++++++++++++ src/plugin/test/SlidingFee.spec.ts | 44 +++++- .../AlgebraBasePluginV1.spec.ts.snap | 2 +- .../AlgebraBasePluginV2.spec.ts.snap | 123 ----------------- .../AlgebraPool.gas.spec.ts.snap | 118 ++++++++--------- .../__snapshots__/SlidingFee.spec.ts.snap | 6 +- src/plugin/test/shared/fixtures.ts | 12 ++ 11 files changed, 301 insertions(+), 211 deletions(-) create mode 100644 src/plugin/test/BasePluginV2Factory.spec.ts delete mode 100644 src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap diff --git a/src/plugin/contracts/AlgebraBasePluginV2.sol b/src/plugin/contracts/AlgebraBasePluginV2.sol index 06d5c3556..ca4ae7726 100644 --- a/src/plugin/contracts/AlgebraBasePluginV2.sol +++ b/src/plugin/contracts/AlgebraBasePluginV2.sol @@ -45,8 +45,16 @@ contract AlgebraBasePluginV2 is SlidingFeePlugin, FarmingProxyPlugin, Volatility 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(); + 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); @@ -70,8 +78,4 @@ contract AlgebraBasePluginV2 is SlidingFeePlugin, FarmingProxyPlugin, Volatility _updatePluginConfigInPool(defaultPluginConfig); // should not be called, reset config return IAlgebraPlugin.afterFlash.selector; } - - function getCurrentFee() external view returns (uint16 fee) { - (, , fee, ) = _getPoolState(); - } -} \ No newline at end of file +} diff --git a/src/plugin/contracts/test/SlidingFeeTest.sol b/src/plugin/contracts/test/SlidingFeeTest.sol index 9d47fa8ca..e2d9cc886 100644 --- a/src/plugin/contracts/test/SlidingFeeTest.sol +++ b/src/plugin/contracts/test/SlidingFeeTest.sol @@ -4,11 +4,13 @@ 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) { @@ -26,4 +28,4 @@ contract SlidingFeeTest is SlidingFeePlugin { function changeFactor(uint16 newFactor) external { s_priceChangeFactor = newFactor; } -} \ No newline at end of file +} 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 index 2b30f8bbd..6c732703d 100644 --- a/src/plugin/test/AlgebraBasePluginV2.spec.ts +++ b/src/plugin/test/AlgebraBasePluginV2.spec.ts @@ -106,14 +106,6 @@ describe('AlgebraBasePluginV2', () => { 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); @@ -318,6 +310,46 @@ describe('AlgebraBasePluginV2', () => { }); }); + 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; @@ -328,6 +360,10 @@ describe('AlgebraBasePluginV2', () => { 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); 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 index 57e5ed6b2..73cc8d4f9 100644 --- a/src/plugin/test/SlidingFee.spec.ts +++ b/src/plugin/test/SlidingFee.spec.ts @@ -24,7 +24,7 @@ describe('SlidingFee', () => { expect(await slidingFeePlugin.s_priceChangeFactor()).to.be.eq(1000) }); - describe('#getSlidingFee', () => { + describe('#FeeFactors', () => { beforeEach('set config', async () => { await slidingFeePlugin.changeBaseFee(500) await slidingFeePlugin.changeFactor(1000) @@ -194,6 +194,48 @@ describe('SlidingFee', () => { }); + 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)); diff --git a/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV1.spec.ts.snap index 369d2135e..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`] = `23774`; +exports[`AlgebraBasePluginV1 AlgebraBasePluginV1 external methods #changeFeeConfiguration feeConfig getter gas cost [ @skip-on-coverage ] 1`] = `23708`; diff --git a/src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap b/src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap deleted file mode 100644 index 63e73906c..000000000 --- a/src/plugin/test/__snapshots__/AlgebraBasePluginV2.spec.ts.snap +++ /dev/null @@ -1,123 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge spike after day 1`] = ` -Array [ - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 123 ", - "Fee: 124 ", - "Fee: 124 ", - "Fee: 124 ", - "Fee: 124 ", - "Fee: 100 ", -] -`; - -exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge spike after initialization 1`] = ` -Array [ - "Fee: 3714 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 2965 ", - "Fee: 2602 ", - "Fee: 1733 ", - "Fee: 995 ", - "Fee: 607 ", - "Fee: 411 ", - "Fee: 309 ", - "Fee: 250 ", - "Fee: 215 ", - "Fee: 191 ", - "Fee: 174 ", - "Fee: 163 ", - "Fee: 154 ", - "Fee: 147 ", - "Fee: 142 ", - "Fee: 139 ", - "Fee: 135 ", - "Fee: 132 ", - "Fee: 130 ", - "Fee: 128 ", - "Fee: 126 ", - "Fee: 100 ", -] -`; - -exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge step after day 1`] = ` -Array [ - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 3000 ", - "Fee: 100 ", -] -`; - -exports[`AlgebraBasePluginV2 #DynamicFeeManager #adaptiveFee single huge step after initialization 1`] = ` -Array [ - "Fee: 15000 ", - "Fee: 15000 ", - "Fee: 15000 ", - "Fee: 15000 ", - "Fee: 14311 ", - "Fee: 11402 ", - "Fee: 7651 ", - "Fee: 5386 ", - "Fee: 4312 ", - "Fee: 3795 ", - "Fee: 3525 ", - "Fee: 3371 ", - "Fee: 3277 ", - "Fee: 3216 ", - "Fee: 3174 ", - "Fee: 3144 ", - "Fee: 3123 ", - "Fee: 3106 ", - "Fee: 3093 ", - "Fee: 3083 ", - "Fee: 3075 ", - "Fee: 3068 ", - "Fee: 3062 ", - "Fee: 3058 ", - "Fee: 3037 ", -] -`; - -exports[`AlgebraBasePluginV2 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 ef3c1b4a2..7371eda9f 100644 --- a/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap +++ b/src/plugin/test/__snapshots__/AlgebraPool.gas.spec.ts.snap @@ -1,42 +1,42 @@ // 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`] = `216020`; +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`] = `168035`; +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`] = `203473`; +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`] = `196259`; +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`] = `164527`; +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`] = `214790`; +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`] = `164537`; +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`] = `198002`; +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`] = `208936`; +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`] = `164009`; +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`] = `117065`; +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`] = `110857`; +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`] = `115657`; +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`] = `126944`; +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`] = `115265`; +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`] = `120065`; +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`] = `126501`; +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`] = `111518`; +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`] = `116318`; +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`] = `52593`; @@ -62,82 +62,82 @@ exports[`AlgebraPool gas tests [ @skip-on-coverage ] Positions #mint below curre 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`] = `157448`; +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`] = `157417`; +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`] = `159202`; +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`] = `174008`; +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`] = `207558`; +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`] = `157515`; +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`] = `207558`; +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`] = `226758`; +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`] = `127681`; +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`] = `127645`; +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`] = `143426`; +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`] = `177797`; +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`] = `234520`; +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`] = `165048`; +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`] = `157509`; +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`] = `157457`; +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`] = `127706`; +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`] = `188859`; +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`] = `188807`; +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`] = `141956`; +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`] = `167935`; +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`] = `167904`; +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`] = `159427`; +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`] = `184732`; +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`] = `218993`; +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`] = `168002`; +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`] = `218993`; +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`] = `238193`; +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`] = `138168`; +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`] = `138132`; +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`] = `154150`; +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`] = `189232`; +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`] = `245955`; +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`] = `165273`; +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`] = `168007`; +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`] = `167955`; +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`] = `138204`; +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`] = `199357`; +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`] = `199305`; +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`] = `152454`; +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 index 6efd0b687..08bddc64a 100644 --- a/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap +++ b/src/plugin/test/__snapshots__/SlidingFee.spec.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SlidingFee #getFee gas cost [ @skip-on-coverage ] gas cost of same tick 1`] = `26753`; +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`] = `31852`; +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`] = `31738`; +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 b6614ef1f..2e464009b 100644 --- a/src/plugin/test/shared/fixtures.ts +++ b/src/plugin/test/shared/fixtures.ts @@ -64,6 +64,18 @@ export const pluginFactoryFixture: Fixture = async functio }; }; +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(); From 00791ade67bc5b83f6d605a5a6beec01e2fb1095 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Tue, 8 Oct 2024 17:08:23 +0300 Subject: [PATCH 56/60] [Common] update natspecs & packages --- src/core/contracts/AlgebraCommunityVault.sol | 2 +- src/core/contracts/AlgebraFactory.sol | 2 +- src/core/contracts/AlgebraPool.sol | 2 +- src/core/contracts/AlgebraPoolDeployer.sol | 2 +- src/core/package.json | 2 +- src/farming/contracts/FarmingCenter.sol | 2 +- .../farmings/AlgebraEternalFarming.sol | 2 +- .../contracts/farmings/EternalVirtualPool.sol | 2 +- src/farming/package.json | 8 ++++---- .../contracts/AlgebraCustomPoolEntryPoint.sol | 2 +- .../contracts/NonfungiblePositionManager.sol | 2 +- src/periphery/contracts/SwapRouter.sol | 2 +- src/periphery/contracts/lens/Quoter.sol | 2 +- src/periphery/contracts/lens/QuoterV2.sol | 2 +- src/periphery/contracts/lens/TickLens.sol | 2 +- src/periphery/package.json | 4 ++-- src/plugin/contracts/AlgebraBasePluginV1.sol | 17 +++++++---------- src/plugin/contracts/AlgebraBasePluginV2.sol | 3 +-- src/plugin/contracts/BasePluginV1Factory.sol | 4 ++-- src/plugin/contracts/BasePluginV2Factory.sol | 6 +++--- src/plugin/contracts/base/BasePlugin.sol | 2 +- .../contracts/lens/AlgebraOracleV1TWAP.sol | 2 +- .../contracts/libraries/VolatilityOracle.sol | 1 - .../contracts/plugins/DynamicFeePlugin.sol | 2 +- .../contracts/plugins/FarmingProxyPlugin.sol | 2 +- .../plugins/VolatilityOraclePlugin.sol | 9 +++------ src/plugin/package.json | 6 +++--- 27 files changed, 43 insertions(+), 51 deletions(-) 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 b54650515..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 diff --git a/src/core/contracts/AlgebraPool.sol b/src/core/contracts/AlgebraPool.sol index a64fe7625..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; 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/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/farming/contracts/FarmingCenter.sol b/src/farming/contracts/FarmingCenter.sol index e5c5d9ea3..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 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.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/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/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 492c02de2..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. 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/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/plugin/contracts/AlgebraBasePluginV1.sol b/src/plugin/contracts/AlgebraBasePluginV1.sol index 53d71da42..36fe6a873 100644 --- a/src/plugin/contracts/AlgebraBasePluginV1.sol +++ b/src/plugin/contracts/AlgebraBasePluginV1.sol @@ -10,17 +10,15 @@ import './plugins/FarmingProxyPlugin.sol'; import './plugins/SlidingFeePlugin.sol'; import './plugins/VolatilityOraclePlugin.sol'; -/// @title Algebra Integral 1.1 default plugin -/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +/// @title Algebra Integral 1.2 adaptive fee plugin contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin { using Plugins for uint8; /// @inheritdoc IAlgebraPlugin - uint8 public constant override defaultPluginConfig = uint8(Plugins.AFTER_INIT_FLAG | Plugins.BEFORE_SWAP_FLAG | Plugins.AFTER_SWAP_FLAG | Plugins.DYNAMIC_FEE); + 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) { - - } + constructor(address _pool, address _factory, address _pluginFactory) BasePlugin(_pool, _factory, _pluginFactory) {} // ###### HOOKS ###### @@ -72,9 +70,8 @@ contract AlgebraBasePluginV1 is DynamicFeePlugin, FarmingProxyPlugin, Volatility return IAlgebraPlugin.afterFlash.selector; } - function getCurrentFee() external view override returns(uint16 fee) { + function getCurrentFee() external view override returns (uint16 fee) { uint88 volatilityAverage = _getAverageVolatilityLast(); - fee =_getCurrentFee(volatilityAverage); - } - + fee = _getCurrentFee(volatilityAverage); + } } diff --git a/src/plugin/contracts/AlgebraBasePluginV2.sol b/src/plugin/contracts/AlgebraBasePluginV2.sol index ca4ae7726..16040b4db 100644 --- a/src/plugin/contracts/AlgebraBasePluginV2.sol +++ b/src/plugin/contracts/AlgebraBasePluginV2.sol @@ -9,8 +9,7 @@ import './plugins/FarmingProxyPlugin.sol'; import './plugins/SlidingFeePlugin.sol'; import './plugins/VolatilityOraclePlugin.sol'; -/// @title Algebra Integral 1.1 default plugin -/// @notice This contract stores timepoints and calculates adaptive fee and statistical averages +/// @title Algebra Integral 1.2 sliding fee plugin contract AlgebraBasePluginV2 is SlidingFeePlugin, FarmingProxyPlugin, VolatilityOraclePlugin { using Plugins for uint8; diff --git a/src/plugin/contracts/BasePluginV1Factory.sol b/src/plugin/contracts/BasePluginV1Factory.sol index d68331dec..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 diff --git a/src/plugin/contracts/BasePluginV2Factory.sol b/src/plugin/contracts/BasePluginV2Factory.sol index adcf07819..512d34c79 100644 --- a/src/plugin/contracts/BasePluginV2Factory.sol +++ b/src/plugin/contracts/BasePluginV2Factory.sol @@ -5,8 +5,8 @@ import './interfaces/IBasePluginV2Factory.sol'; import './AlgebraBasePluginV2.sol'; import './interfaces/plugins/ISlidingFeePlugin.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 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 @@ -76,4 +76,4 @@ contract BasePluginV2Factory is IBasePluginV2Factory { defaultBaseFee = newDefaultBaseFee; emit DefaultBaseFee(newDefaultBaseFee); } -} \ No newline at end of file +} diff --git a/src/plugin/contracts/base/BasePlugin.sol b/src/plugin/contracts/base/BasePlugin.sol index 3713cf7b1..1e0d63a05 100644 --- a/src/plugin/contracts/base/BasePlugin.sol +++ b/src/plugin/contracts/base/BasePlugin.sol @@ -11,7 +11,7 @@ import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; import '../interfaces/IBasePlugin.sol'; -/// @title Algebra Integral 1.1 default plugin +/// @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; 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 index 77b6ea4b9..ed8dcf679 100644 --- a/src/plugin/contracts/plugins/DynamicFeePlugin.sol +++ b/src/plugin/contracts/plugins/DynamicFeePlugin.sol @@ -13,7 +13,7 @@ import '../libraries/AdaptiveFee.sol'; import '../types/AlgebraFeeConfigurationU144.sol'; import '../base/BasePlugin.sol'; -/// @title Algebra Integral 1.1 default plugin +/// @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; diff --git a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol index 39bfb6d39..cab8d57f5 100644 --- a/src/plugin/contracts/plugins/FarmingProxyPlugin.sol +++ b/src/plugin/contracts/plugins/FarmingProxyPlugin.sol @@ -11,7 +11,7 @@ import '../interfaces/plugins/IFarmingPlugin.sol'; import '../base/BasePlugin.sol'; -/// @title Algebra Integral 1.1 default plugin +/// @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; diff --git a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol index f5bef6727..5afe8bc78 100644 --- a/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol +++ b/src/plugin/contracts/plugins/VolatilityOraclePlugin.sol @@ -10,9 +10,9 @@ import '../interfaces/plugins/IVolatilityOracle.sol'; import '../libraries/VolatilityOracle.sol'; import '../base/BasePlugin.sol'; -/// @title Algebra Integral 1.1 default plugin +/// @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{ +abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle { using Plugins for uint8; uint256 internal constant UINT16_MODULO = 65536; @@ -39,11 +39,9 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ (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; @@ -103,7 +101,6 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ } function _getAverageVolatilityLast() internal view returns (uint88 volatilityAverage) { - uint32 currentTimestamp = _blockTimestamp(); (, int24 tick, , ) = _getPoolState(); @@ -113,7 +110,7 @@ abstract contract VolatilityOraclePlugin is BasePlugin, IVolatilityOracle{ volatilityAverage = timepoints.getAverageVolatility(currentTimestamp, tick, lastTimepointIndex, oldestIndex); } - function _getLastTick() internal view returns(int24 lastTick) { + function _getLastTick() internal view returns (int24 lastTick) { VolatilityOracle.Timepoint memory lastTimepoint = timepoints[timepointIndex]; return lastTimepoint.tick; } 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", From 66396ab58d5ac80255a1602e0a44077676f171d1 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Wed, 9 Oct 2024 17:25:26 +0300 Subject: [PATCH 57/60] [Common] update package-lock --- src/core/package-lock.json | 4 +- src/farming/package-lock.json | 34 ++++----- src/periphery/package-lock.json | 17 ++++- src/plugin/package-lock.json | 124 ++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 src/plugin/package-lock.json 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/farming/package-lock.json b/src/farming/package-lock.json index 70589eed9..5199c8703 100644 --- a/src/farming/package-lock.json +++ b/src/farming/package-lock.json @@ -1,17 +1,17 @@ { "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", + "@cryptoalgebra/integral-base-plugin": "1.2.0", + "@cryptoalgebra/integral-core": "1.2.0", + "@cryptoalgebra/integral-periphery": "1.2.0", "@openzeppelin/contracts": "4.9.3" }, "devDependencies": { @@ -24,12 +24,12 @@ } }, "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==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-base-plugin/-/integral-base-plugin-1.2.0.tgz", + "integrity": "sha512-sgP6POAGgQj3AWLdun4VsoOKVx4S9Zr4dfEOkLZYeqKzqI/KRhMdodt+wqd+mwcocXXuIh9e3JmF0rm1db7mqA==", "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" }, "engines": { "node": ">=16.0.0", @@ -37,9 +37,9 @@ } }, "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==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.2.0.tgz", + "integrity": "sha512-BKi/iOj3QflrBOeV5slpqXszpkNEirEd1qXwJxx4bNuTu9lih3aSorvhoNdgg6Jr5wlDuZrw/JrGvOIj4OqhbQ==", "dependencies": { "@openzeppelin/contracts": "4.9.3" }, @@ -49,11 +49,11 @@ } }, "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==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-periphery/-/integral-periphery-1.2.0.tgz", + "integrity": "sha512-gH3qLqoDHeq3qf0Pe277v7ei1712wGsYvW73KplNOtXtsPBmh4T3L6ZvL0DokZNvf676xdhyqo2zFp8Ip6ojNg==", "dependencies": { - "@cryptoalgebra/integral-core": "1.1.0", + "@cryptoalgebra/integral-core": "1.2.0", "@openzeppelin/contracts": "4.9.3", "@uniswap/v2-core": "1.0.1" }, diff --git a/src/periphery/package-lock.json b/src/periphery/package-lock.json index 0e3b96d55..4cdedd32b 100644 --- a/src/periphery/package-lock.json +++ b/src/periphery/package-lock.json @@ -1,14 +1,15 @@ { "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": { + "@cryptoalgebra/integral-core": "1.2.0", "@openzeppelin/contracts": "4.9.3", "@uniswap/v2-core": "1.0.1" }, @@ -20,6 +21,18 @@ "npm": ">=8.0.0" } }, + "node_modules/@cryptoalgebra/integral-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.2.0.tgz", + "integrity": "sha512-BKi/iOj3QflrBOeV5slpqXszpkNEirEd1qXwJxx4bNuTu9lih3aSorvhoNdgg6Jr5wlDuZrw/JrGvOIj4OqhbQ==", + "dependencies": { + "@openzeppelin/contracts": "4.9.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, "node_modules/@openzeppelin/contracts": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.3.tgz", 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 + } + } +} From 96ff0aa089ed70b17d069db31aead39fe96e3262 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 10 Oct 2024 04:32:09 +0300 Subject: [PATCH 58/60] [Farming] update farming tests snapshots --- src/farming/test/unit/EternalFarms.spec.ts | 33 ++++++++++++++----- src/farming/test/unit/FarmingCenter.spec.ts | 16 ++++----- .../__snapshots__/EternalFarms.spec.ts.snap | 4 +-- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/farming/test/unit/EternalFarms.spec.ts b/src/farming/test/unit/EternalFarms.spec.ts index 780b77717..d0eb4939c 100644 --- a/src/farming/test/unit/EternalFarms.spec.ts +++ b/src/farming/test/unit/EternalFarms.spec.ts @@ -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); @@ -1643,7 +1658,7 @@ describe('unit/EternalFarms', () => { await context.rewardToken.getAddress(), await context.bonusRewardToken.getAddress(), lpUser0.address, - 9999n, + 9079n, 199n ); }); diff --git a/src/farming/test/unit/FarmingCenter.spec.ts b/src/farming/test/unit/FarmingCenter.spec.ts index 58f91bb65..c1542518a 100644 --- a/src/farming/test/unit/FarmingCenter.spec.ts +++ b/src/farming/test/unit/FarmingCenter.spec.ts @@ -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/__snapshots__/EternalFarms.spec.ts.snap b/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap index 2616188c3..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`] = `506125`; +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`] = `175750`; +exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `177134`; From 755a2cf5217cb8fc4e089843225812fcd2c22f31 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Thu, 10 Oct 2024 12:03:25 +0300 Subject: [PATCH 59/60] [Periphery] update snapshots --- src/farming/package-lock.json | 50 ------------------- src/periphery/package-lock.json | 13 ----- .../NonfungiblePositionManager.spec.ts.snap | 6 +-- 3 files changed, 3 insertions(+), 66 deletions(-) diff --git a/src/farming/package-lock.json b/src/farming/package-lock.json index 5199c8703..342dda438 100644 --- a/src/farming/package-lock.json +++ b/src/farming/package-lock.json @@ -9,9 +9,6 @@ "version": "1.2.0", "license": "GPL-3.0-or-later", "dependencies": { - "@cryptoalgebra/integral-base-plugin": "1.2.0", - "@cryptoalgebra/integral-core": "1.2.0", - "@cryptoalgebra/integral-periphery": "1.2.0", "@openzeppelin/contracts": "4.9.3" }, "devDependencies": { @@ -23,45 +20,6 @@ "npm": ">=8.0.0" } }, - "node_modules/@cryptoalgebra/integral-base-plugin": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-base-plugin/-/integral-base-plugin-1.2.0.tgz", - "integrity": "sha512-sgP6POAGgQj3AWLdun4VsoOKVx4S9Zr4dfEOkLZYeqKzqI/KRhMdodt+wqd+mwcocXXuIh9e3JmF0rm1db7mqA==", - "dependencies": { - "@cryptoalgebra/integral-core": "1.2.0", - "@cryptoalgebra/integral-periphery": "1.2.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@cryptoalgebra/integral-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.2.0.tgz", - "integrity": "sha512-BKi/iOj3QflrBOeV5slpqXszpkNEirEd1qXwJxx4bNuTu9lih3aSorvhoNdgg6Jr5wlDuZrw/JrGvOIj4OqhbQ==", - "dependencies": { - "@openzeppelin/contracts": "4.9.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, - "node_modules/@cryptoalgebra/integral-periphery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-periphery/-/integral-periphery-1.2.0.tgz", - "integrity": "sha512-gH3qLqoDHeq3qf0Pe277v7ei1712wGsYvW73KplNOtXtsPBmh4T3L6ZvL0DokZNvf676xdhyqo2zFp8Ip6ojNg==", - "dependencies": { - "@cryptoalgebra/integral-core": "1.2.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/periphery/package-lock.json b/src/periphery/package-lock.json index 4cdedd32b..aff7404a2 100644 --- a/src/periphery/package-lock.json +++ b/src/periphery/package-lock.json @@ -9,7 +9,6 @@ "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" }, @@ -21,18 +20,6 @@ "npm": ">=8.0.0" } }, - "node_modules/@cryptoalgebra/integral-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.2.0.tgz", - "integrity": "sha512-BKi/iOj3QflrBOeV5slpqXszpkNEirEd1qXwJxx4bNuTu9lih3aSorvhoNdgg6Jr5wlDuZrw/JrGvOIj4OqhbQ==", - "dependencies": { - "@openzeppelin/contracts": "4.9.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - } - }, "node_modules/@openzeppelin/contracts": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.3.tgz", diff --git a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap index 07ca780c7..8f61d2cf7 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -26,14 +26,14 @@ exports[`NonfungiblePositionManager #mint gas mint for same pool, different tick exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `332144`; -exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60003`; +exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60036`; -exports[`NonfungiblePositionManager #permit owned by verifying contract gas [ @skip-on-coverage ] 1`] = `63869`; +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`] = `22015`; +exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `22035`; exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `247656`; From 2195026c570fb6036dd11e3d8dfdd98a7916b3e5 Mon Sep 17 00:00:00 2001 From: IliaAzhel Date: Fri, 11 Oct 2024 12:27:41 +0300 Subject: [PATCH 60/60] [Common] add integral v1.2 core diff audit --- ..._Algebra_Integralv1.2_Core_Update_Audit.pdf | Bin 0 -> 233093 bytes audits/README.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 audits/Bailsec_Algebra_Integralv1.2_Core_Update_Audit.pdf 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 0000000000000000000000000000000000000000..600d744543e7e2df3dd4e1ccb76252d4010369d3 GIT binary patch literal 233093 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 Gn7AAt31(^UCeS!|1OULlHq76Qsbt76 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 Cr! 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�qD!-YQZtmQ09$mzx^?`WWmnD9vmy8a7dAKAP$@sK^C&K40{eRNmu18Q$VF$=pJo7{b)2mCqS(ssY6^PsP}rijo5 z8~fxi`|c<0r59H65&6btM(uYQX}Bj{rx#xJtQN|g`ixlOMmFQcu0@n z5>&NyRQ7w49!?X=I9{~f+QEACXGm0?*KK{NH2r?Q56@*^KI7wj=M3~$u`F!a*n^HQ z)6UhokPN_d?P2vZiI)szpk^S$$KaC3xjOQntx-Wg<$t*K>P`}GCxv-XY98oGJ9HZX zTtP#-NVlxim;d$$lwkwcJZF}bq;Gb|X#S%$o)EgI7g+jK;2H6DfK=Pn@M@W)!h4W} zKnf-yShU*yq!Cp61C8)4xCHw>sHT9}ik+}|7`~eHuMsB0qJn<44Lmf0@^IjtZE*je zmz=U65XTQe-T$Xn|3mJ7w(=al^NSw)*9fQ1#1r1TwtvV=JQ~nl9$aXl+4UG=!Viwn zD*LiXOYz_U-t#|psvvwBrK&zm4X>x;S zbG-zK3kT9O{Gi{zw~FmBj=U%23>KLFHTOL11e>STP_D?)wXFcD%|mEsc(hbV4R9$u zl*{*zR&+CFhINvdSU8f56&cTAHh$396CyOg5DX}`-zDXafT|hdfXpek-#HB!EB)14 zO0k`tR4_jGSL-gg8Q*6>|F70}|GAZp5TfEei)9>ntYsqck5=1N(Q1_)kdiq6*W9(y&5CnV z|EH9Coo-pLuP93Zm{@3G?EwO$EgE}(C4>eDT9y8DYuOp_?8gF7>O{Qz11)&gsD}Qp zXF;pdzqJz4b6~h&Ow8N^$rwTCDReP{7+(l3F#s-qQ<+R~8S7$n<{zy`=*hZQaqBNY z7eeUqbjhp!3obbTJz7vM>PSU|0eZ@v^WOc{Iso_o)dmtEf3@y|n>X+|FaOp0^FOx& zApZI{ApY-!>0O>9^!&|`WI^chkg|d-E|?rr62RqeDsv57(n(o0|D*K?J<|U&Br*_s zl1gWD|3^v<;FA46h6{k&-@yccnEf{({`W97pdg|r=wF6pMgXA43S>n?iTXz!uMXhy zHLMMx!|1-Wvij>W>)YG!=FEA+rZ0)0RIsUTFQsDAGw_XF!{vC+_TVa}b zgr5KJd`}=iz<+=XZf9{E5G25YYmXRGK^FWKqPBidx#8S(b_^Duo4R}@DC7>P z5Vduyg@~KlpnTCS5A{hgr2Gjns6GK>v+-i0jXH)Nfw7rv$Vq$G_`ZRFKS!BgJ2l6^ zz1B=-Y`kHps8xR=8q^(tgtL^5yMZI$r)Suy+*EgDczZ6yRsmL1dDK-f9E9N4&=E%R z3-OQlBWa;Aoo757RKyZ)cN``T8_z>cMTpl?KY^aO1rH2(|NRa(C7Kh4DZDoxe%J3P zL`BT|#o0AkLagq*MNcY<(%--{4SFJyYCbT6yGR2iNi}cX!|G2T?@N&bl?zP5txqqr z^i9siET4eM8h=zVhTEG;-ur9ca@(JfHHTPr;}NTW7)CHkAJEr>g2zDw0hL=_!3b(e#(7%hPgp+K2H!25%dH-+H~G67Bq?C94G4 zpUZH!0y#dJbyW6PyoVg!!U+I;Gz(Q1XAZo<&xH$GA~~`}`36rkmL9fMc<*NiedVnu z@z4%tAKotSx7F6GW2Cf2IdAbS^@phYQZ+3k@n*8f^W)7i873O+&AIi0VGgB>FInj- zdLKnYTR*V4ug5;~k#10mhhT9i{FQmP&>G|2Y`j|uL?+v6qaUm6Cx_{7yns=dc-~PL zjEr#n)@aT@tOJQylsahcl(MsE5mtljc$`_^P0mkNPC>nwIBcP^@()wb31d^(%kA9OT^N>)oeT>)B*UPH#X@% zNK_n6RG$%mO&V`hQP}i-@K|e=8uE0t2QDA{`*ht?ERBTe!W`v^!gqyV zVa$Coi)JPe*8+|JSf#Hw`&U1u@Iypf_hfOg4Ky0iYRjr+WSBy&d~Y^|STyHtcUav( z*FM#!YVxwIq!K&VLE{𝔐8E#Zp^YVn%Xo~*}a!EuBR72zj<8JTx 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

M^=o-HO2Z6&N6~aav)<_S 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