diff --git a/audits/Base_Plugin_report_MixBytes.pdf b/audits/Base_Plugin_report_MixBytes.pdf new file mode 100644 index 000000000..691434514 Binary files /dev/null and b/audits/Base_Plugin_report_MixBytes.pdf differ diff --git a/audits/Farming_Plugin_report_MixBytes.pdf b/audits/Farming_Plugin_report_MixBytes.pdf index 691434514..3e44b7446 100644 Binary files a/audits/Farming_Plugin_report_MixBytes.pdf and b/audits/Farming_Plugin_report_MixBytes.pdf differ diff --git a/audits/README.md b/audits/README.md index be498b1c1..c99bc4f61 100644 --- a/audits/README.md +++ b/audits/README.md @@ -2,9 +2,9 @@ | Module | Auditor | Report | Auditor publication | | :---------------- | :---------------- | :------: | ----: | -| Core | [MixBytes](https://mixbytes.io/) | [report](https://github.com/cryptoalgebra/Algebra/blob/3cd27234278a956fafcb8249242ec413eaecc25c/audits/Core_audit_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Core/Algebra%20Finance%20Core%20Security%20Audit%20Report.pdf) | -| Farming | [MixBytes](https://mixbytes.io/) | [report](https://github.com/cryptoalgebra/Algebra/blob/3cd27234278a956fafcb8249242ec413eaecc25c/audits/Farming_Plugin_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Farmings/Algebra%20Farmings%20Security%20Audit%20Report.pdf) | -| Base plugin | [MixBytes](https://mixbytes.io/) | [report](https://github.com/cryptoalgebra/Algebra/blob/3cd27234278a956fafcb8249242ec413eaecc25c/audits/Farming_Plugin_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Plugins/Algebra%20Plugins%20Security%20Audit%20Report.pdf) | +| Core | [MixBytes](https://mixbytes.io/) | [report](Core_audit_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Core/Algebra%20Finance%20Core%20Security%20Audit%20Report.pdf) | +| Farming | [MixBytes](https://mixbytes.io/) | [report](Farming_Plugin_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Farmings/Algebra%20Farmings%20Security%20Audit%20Report.pdf) | +| Base plugin | [MixBytes](https://mixbytes.io/) | [report](Base_Plugin_report_MixBytes.pdf) | [MixBytes repository](https://github.com/mixbytes/audits_public/blob/master/Algebra%20Finance/Plugins/Algebra%20Plugins%20Security%20Audit%20Report.pdf) | | Entire protocol, bug hunting | [Riley Holterhus](https://www.rileyholterhus.com/) | [report](https://github.com/cryptoalgebra/Algebra/blob/dev/audits/Riley_Holterhus_Algebra_Integral.pdf) | | | Entire protocol | [Paladin](https://paladinsec.co/) | [report](Algebra_Paladin_report.pdf) | | | Custom Pools | [Bailsec](https://bailsec.io/) | [report](Bailsec_Algebra_Integral_Update_Audit_differential_Report.pdf) | | diff --git a/src/farming/contracts/FarmingCenter.sol b/src/farming/contracts/FarmingCenter.sol index 722fee30c..b704a964e 100644 --- a/src/farming/contracts/FarmingCenter.sol +++ b/src/farming/contracts/FarmingCenter.sol @@ -84,7 +84,7 @@ contract FarmingCenter is IFarmingCenter, IPositionFollower, Multicall { bytes32 _eternalIncentiveId = deposits[tokenId]; if (_eternalIncentiveId != bytes32(0)) { address tokenOwner = nonfungiblePositionManager.ownerOf(tokenId); - (, , , , , , uint128 liquidity, , , , ) = nonfungiblePositionManager.positions(tokenId); + (, , , , , , , uint128 liquidity, , , , ) = nonfungiblePositionManager.positions(tokenId); IncentiveKey memory key = incentiveKeys[_eternalIncentiveId]; @@ -140,6 +140,9 @@ contract FarmingCenter is IFarmingCenter, IPositionFollower, Multicall { require(msg.sender == address(eternalFarming), 'Only farming can call this'); require(virtualPool != address(0), 'Zero address as virtual pool'); pool = IAlgebraPool(plugin.pool()); - require(address(pool) == PoolAddress.computeAddress(algebraPoolDeployer, PoolAddress.PoolKey(pool.token0(), pool.token1())), 'Invalid pool'); + require( + address(pool) == PoolAddress.computeAddress(algebraPoolDeployer, PoolAddress.PoolKey(address(0), pool.token0(), pool.token1())), + 'Invalid pool' + ); } } diff --git a/src/farming/contracts/libraries/NFTPositionInfo.sol b/src/farming/contracts/libraries/NFTPositionInfo.sol index fd339c547..363916b2b 100644 --- a/src/farming/contracts/libraries/NFTPositionInfo.sol +++ b/src/farming/contracts/libraries/NFTPositionInfo.sol @@ -22,8 +22,11 @@ library NFTPositionInfo { ) internal view returns (IAlgebraPool pool, int24 tickLower, int24 tickUpper, uint128 liquidity) { address token0; address token1; - (, , token0, token1, tickLower, tickUpper, liquidity, , , , ) = nonfungiblePositionManager.positions(tokenId); + address pluginDeployer; + (, , token0, token1, pluginDeployer, tickLower, tickUpper, liquidity, , , , ) = nonfungiblePositionManager.positions(tokenId); - pool = IAlgebraPool(PoolAddress.computeAddress(address(deployer), PoolAddress.PoolKey({token0: token0, token1: token1}))); + pool = IAlgebraPool( + PoolAddress.computeAddress(address(deployer), PoolAddress.PoolKey({deployer: pluginDeployer, token0: token0, token1: token1})) + ); } } diff --git a/src/farming/package-lock.json b/src/farming/package-lock.json index 961969a1e..70589eed9 100644 --- a/src/farming/package-lock.json +++ b/src/farming/package-lock.json @@ -9,6 +9,9 @@ "version": "1.1.0", "license": "GPL-3.0-or-later", "dependencies": { + "@cryptoalgebra/integral-base-plugin": "1.1.0", + "@cryptoalgebra/integral-core": "1.1.0", + "@cryptoalgebra/integral-periphery": "1.1.0", "@openzeppelin/contracts": "4.9.3" }, "devDependencies": { @@ -20,6 +23,45 @@ "npm": ">=8.0.0" } }, + "node_modules/@cryptoalgebra/integral-base-plugin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-base-plugin/-/integral-base-plugin-1.1.0.tgz", + "integrity": "sha512-fWpdTjIf1VFLB+qQSU9TP7BdgWrRjlxIBVYEN4I8BtfIIwr8Ay3QrYYsUvI2FFdN2I5HroZLanmgYS0+IjXLDg==", + "dependencies": { + "@cryptoalgebra/integral-core": "1.1.0", + "@cryptoalgebra/integral-periphery": "1.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "node_modules/@cryptoalgebra/integral-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-core/-/integral-core-1.1.0.tgz", + "integrity": "sha512-SESM8dIrNd2vkLsiYDBZxewmsz4ZyhfuVIe2SNTCCayl14W2dUbZ1y4qWRqSZ6JPKIFOHdrdLoNk+fFIC7Ukvg==", + "dependencies": { + "@openzeppelin/contracts": "4.9.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "node_modules/@cryptoalgebra/integral-periphery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cryptoalgebra/integral-periphery/-/integral-periphery-1.1.0.tgz", + "integrity": "sha512-Z+/FVtucH2GeoNuMcf+3Z859fNIOBNJTHSynu+UVV9NtMia9c7olEzcaPpyFkE7kynQgUcc+KffgE7KFDS0H+Q==", + "dependencies": { + "@cryptoalgebra/integral-core": "1.1.0", + "@openzeppelin/contracts": "4.9.3", + "@uniswap/v2-core": "1.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, "node_modules/@openzeppelin/contracts": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.3.tgz", @@ -31,6 +73,14 @@ "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", "dev": true }, + "node_modules/@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", diff --git a/src/farming/test/helpers/index.ts b/src/farming/test/helpers/index.ts index 91c408424..4e2cb9c6b 100644 --- a/src/farming/test/helpers/index.ts +++ b/src/farming/test/helpers/index.ts @@ -1,5 +1,5 @@ import { Wallet, MaxUint256, Interface } from 'ethers'; -import { blockTimestamp, BNe18, FeeAmount, getCurrentTick, maxGas, encodePath, arrayWrap, getMinTick, getMaxTick } from '../shared/index'; +import { blockTimestamp, BNe18, FeeAmount, getCurrentTick, maxGas, encodePath, arrayWrap, getMinTick, getMaxTick, ZERO_ADDRESS } from '../shared/index'; import _ from 'lodash'; import { TestERC20, INonfungiblePositionManager, AlgebraEternalFarming, IAlgebraPool, TestIncentiveId, FarmingCenter } from '../../typechain'; import abi from '../../artifacts/contracts/farmings/EternalVirtualPool.sol/EternalVirtualPool.json'; @@ -343,7 +343,7 @@ export class HelperCommands { const erc20Helper = new ERC20Helper(); await erc20Helper.ensureBalancesAndApprovals(actor, [tok0, tok1], amountIn, await this.router.getAddress()); - const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, tok0Address] : [tok0Address, tok1Address]); + const path = encodePath(MAKE_TICK_GO_UP ? [tok1Address, ZERO_ADDRESS, tok0Address] : [tok0Address, ZERO_ADDRESS, tok1Address]); await this.router.connect(actor).exactInput( { @@ -410,6 +410,7 @@ export class HelperCommands { deadline: MaxUint256, tokenIn: zto ? tok0Address : tok1Address, tokenOut: zto ? tok1Address : tok0Address, + deployer: ZERO_ADDRESS, amountIn: 2n ** 128n - 1n, amountOutMinimum: 0, limitSqrtPrice: priceAtTarget, diff --git a/src/farming/test/shared/fixtures.ts b/src/farming/test/shared/fixtures.ts index 09d476ba8..008bd967a 100644 --- a/src/farming/test/shared/fixtures.ts +++ b/src/farming/test/shared/fixtures.ts @@ -31,7 +31,7 @@ import { TestIncentiveId, FarmingCenter, } from '../../typechain'; -import { FeeAmount, encodePriceSqrt, MAX_GAS_LIMIT } from '../shared'; +import { FeeAmount, encodePriceSqrt, MAX_GAS_LIMIT, ZERO_ADDRESS } from '../shared'; import { ActorFixture } from './actors'; import { IBasePluginV1Factory, IAlgebraBasePluginV1 } from '@cryptoalgebra/integral-base-plugin/typechain'; @@ -182,6 +182,7 @@ export const mintPosition = async ( { token0: mintParams.token0, token1: mintParams.token1, + deployer: ZERO_ADDRESS, tickLower: mintParams.tickLower, tickUpper: mintParams.tickUpper, recipient: mintParams.recipient, @@ -272,9 +273,9 @@ export const algebraFixture: () => Promise = async () => { const fee = FeeAmount.MEDIUM; - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); - await nft.createAndInitializePoolIfNecessary(tokens[1], tokens[2], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[1], tokens[2], ZERO_ADDRESS, encodePriceSqrt(1, 1)); 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 d04202957..6f337cdf6 100644 --- a/src/farming/test/unit/EternalFarms.spec.ts +++ b/src/farming/test/unit/EternalFarms.spec.ts @@ -1898,7 +1898,7 @@ describe('unit/EternalFarms', () => { await erc20Helper.ensureBalancesAndApprovals(lpUser0, [token0, token1], amountDesired, await context.nft.getAddress()); - await context.nft.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(1, 1)); + await context.nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); const poolAddress = await context.factory.poolByPair(token0, token1); @@ -1958,6 +1958,7 @@ describe('unit/EternalFarms', () => { const swapData = { tokenIn: tokenReentrant, tokenOut: context.token1, + deployer: ZERO_ADDRESS, amountIn: 10, amountOutMinimum: 0, recipient: lpUser0.address, diff --git a/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap b/src/farming/test/unit/__snapshots__/EternalFarms.spec.ts.snap index 51f80a875..50df3ca84 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`] = `494839`; +exports[`unit/EternalFarms #enterFarming works and has gas cost [ @skip-on-coverage ] 1`] = `498891`; -exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `169695`; +exports[`unit/EternalFarms #exitFarming after end time works and has gas cost [ @skip-on-coverage ] 1`] = `171745`; diff --git a/src/periphery/contracts/NonfungiblePositionManager.sol b/src/periphery/contracts/NonfungiblePositionManager.sol index 56656373d..7e1de9349 100644 --- a/src/periphery/contracts/NonfungiblePositionManager.sol +++ b/src/periphery/contracts/NonfungiblePositionManager.sol @@ -108,6 +108,7 @@ contract NonfungiblePositionManager is address operator, address token0, address token1, + address deployer, int24 tickLower, int24 tickUpper, uint128 liquidity, @@ -130,6 +131,7 @@ contract NonfungiblePositionManager is position.operator, poolKey.token0, poolKey.token1, + poolKey.deployer, tickLower, tickUpper, liquidity, @@ -156,6 +158,7 @@ contract NonfungiblePositionManager is AddLiquidityParams({ token0: params.token0, token1: params.token1, + deployer: params.deployer, recipient: address(this), tickLower: params.tickLower, tickUpper: params.tickUpper, @@ -178,7 +181,7 @@ contract NonfungiblePositionManager is // idempotent set uint80 poolId = _cachePoolKey( address(pool), - PoolAddress.PoolKey({token0: params.token0, token1: params.token1}) + PoolAddress.PoolKey({deployer: params.deployer, token0: params.token0, token1: params.token1}) ); _positions[tokenId] = Position({ @@ -268,6 +271,7 @@ contract NonfungiblePositionManager is AddLiquidityParams({ token0: poolKey.token0, token1: poolKey.token1, + deployer: poolKey.deployer, tickLower: tickLower, tickUpper: tickUpper, amount0Desired: params.amount0Desired, @@ -409,7 +413,7 @@ contract NonfungiblePositionManager is /// @inheritdoc INonfungiblePositionManager function burn(uint256 tokenId) external payable override isAuthorizedForToken(tokenId) { Position storage position = _positions[tokenId]; - require(position.liquidity | position.tokensOwed0 | position.tokensOwed1 == 0, 'Not cleared'); + require(position.liquidity | position.tokensOwed0 | position.tokensOwed1 == 0); delete _positions[tokenId]; delete tokenFarmedIn[tokenId]; @@ -424,7 +428,7 @@ contract NonfungiblePositionManager is ) external payable override isAuthorizedForToken(tokenId) { address newValue; if (approve) { - require(farmingAddress == farmingCenter, 'Invalid farming address'); + require(farmingAddress == farmingCenter); newValue = farmingAddress; } farmingApprovals[tokenId] = newValue; @@ -450,7 +454,6 @@ contract NonfungiblePositionManager is function setFarmingCenter(address newFarmingCenter) external override { require(IAlgebraFactory(factory).hasRoleOrOwner(NONFUNGIBLE_POSITION_MANAGER_ADMINISTRATOR_ROLE, msg.sender)); farmingCenter = newFarmingCenter; - emit FarmingCenter(newFarmingCenter); } /// @inheritdoc IERC721Metadata diff --git a/src/periphery/contracts/NonfungibleTokenPositionDescriptor.sol b/src/periphery/contracts/NonfungibleTokenPositionDescriptor.sol index 4dc6c887b..1b6e95fdb 100644 --- a/src/periphery/contracts/NonfungibleTokenPositionDescriptor.sol +++ b/src/periphery/contracts/NonfungibleTokenPositionDescriptor.sol @@ -66,14 +66,25 @@ contract NonfungibleTokenPositionDescriptor is INonfungibleTokenPositionDescript INonfungiblePositionManager positionManager, uint256 tokenId ) external view override returns (string memory) { - (, , address token0, address token1, int24 tickLower, int24 tickUpper, , , , , ) = positionManager.positions( - tokenId - ); + ( + , + , + address token0, + address token1, + address deployer, + int24 tickLower, + int24 tickUpper, + , + , + , + , + + ) = positionManager.positions(tokenId); IAlgebraPool pool = IAlgebraPool( PoolAddress.computeAddress( positionManager.poolDeployer(), - PoolAddress.PoolKey({token0: token0, token1: token1}) + PoolAddress.PoolKey({deployer: deployer, token0: token0, token1: token1}) ) ); diff --git a/src/periphery/contracts/SwapRouter.sol b/src/periphery/contracts/SwapRouter.sol index 623945317..5bba6bdab 100644 --- a/src/periphery/contracts/SwapRouter.sol +++ b/src/periphery/contracts/SwapRouter.sol @@ -44,8 +44,8 @@ contract SwapRouter is ) PeripheryImmutableState(_factory, _WNativeToken, _poolDeployer) {} /// @dev Returns the pool for the given token pair. The pool contract may or may not exist. - function getPool(address tokenA, address tokenB) private view returns (IAlgebraPool) { - return IAlgebraPool(PoolAddress.computeAddress(poolDeployer, PoolAddress.getPoolKey(tokenA, tokenB))); + function getPool(address deployer, address tokenA, address tokenB) private view returns (IAlgebraPool) { + return IAlgebraPool(PoolAddress.computeAddress(poolDeployer, PoolAddress.getPoolKey(deployer, tokenA, tokenB))); } struct SwapCallbackData { @@ -57,8 +57,8 @@ contract SwapRouter is function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) external override { require(amount0Delta > 0 || amount1Delta > 0, 'Zero liquidity swap'); // swaps entirely within 0-liquidity regions are not supported SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); - (address tokenIn, address tokenOut) = data.path.decodeFirstPool(); - CallbackValidation.verifyCallback(poolDeployer, tokenIn, tokenOut); + (address tokenIn, address deployer, address tokenOut) = data.path.decodeFirstPool(); + CallbackValidation.verifyCallback(poolDeployer, deployer, tokenIn, tokenOut); (bool isExactInput, uint256 amountToPay) = amount0Delta > 0 ? (tokenIn < tokenOut, uint256(amount0Delta)) @@ -87,11 +87,11 @@ contract SwapRouter is ) private returns (uint256 amountOut) { if (recipient == address(0)) recipient = address(this); // allow swapping to the router address with address 0 - (address tokenIn, address tokenOut) = data.path.decodeFirstPool(); + (address tokenIn, address deployer, address tokenOut) = data.path.decodeFirstPool(); bool zeroToOne = tokenIn < tokenOut; - (int256 amount0, int256 amount1) = getPool(tokenIn, tokenOut).swap( + (int256 amount0, int256 amount1) = getPool(deployer, tokenIn, tokenOut).swap( recipient, zeroToOne, amountIn.toInt256(), @@ -112,7 +112,10 @@ contract SwapRouter is params.amountIn, params.recipient, params.limitSqrtPrice, - SwapCallbackData({path: abi.encodePacked(params.tokenIn, params.tokenOut), payer: msg.sender}) + SwapCallbackData({ + path: abi.encodePacked(params.tokenIn, params.deployer, params.tokenOut), + payer: msg.sender + }) ); require(amountOut >= params.amountOutMinimum, 'Too little received'); } @@ -155,23 +158,24 @@ contract SwapRouter is ExactInputSingleParams calldata params ) external payable override checkDeadline(params.deadline) returns (uint256 amountOut) { SwapCallbackData memory data = SwapCallbackData({ - path: abi.encodePacked(params.tokenIn, params.tokenOut), + path: abi.encodePacked(params.tokenIn, params.deployer, params.tokenOut), payer: msg.sender }); address recipient = params.recipient == address(0) ? address(this) : params.recipient; bool zeroToOne = params.tokenIn < params.tokenOut; - (int256 amount0, int256 amount1) = getPool(params.tokenIn, params.tokenOut).swapWithPaymentInAdvance( - msg.sender, - recipient, - zeroToOne, - params.amountIn.toInt256(), - params.limitSqrtPrice == 0 - ? (zeroToOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) - : params.limitSqrtPrice, - abi.encode(data) - ); + (int256 amount0, int256 amount1) = getPool(params.deployer, params.tokenIn, params.tokenOut) + .swapWithPaymentInAdvance( + msg.sender, + recipient, + zeroToOne, + params.amountIn.toInt256(), + params.limitSqrtPrice == 0 + ? (zeroToOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + : params.limitSqrtPrice, + abi.encode(data) + ); amountOut = uint256(-(zeroToOne ? amount1 : amount0)); @@ -187,11 +191,11 @@ contract SwapRouter is ) private returns (uint256 amountIn) { if (recipient == address(0)) recipient = address(this); // allow swapping to the router address with address 0 - (address tokenOut, address tokenIn) = data.path.decodeFirstPool(); + (address tokenOut, address deployer, address tokenIn) = data.path.decodeFirstPool(); bool zeroToOne = tokenIn < tokenOut; - (int256 amount0Delta, int256 amount1Delta) = getPool(tokenIn, tokenOut).swap( + (int256 amount0Delta, int256 amount1Delta) = getPool(deployer, tokenIn, tokenOut).swap( recipient, zeroToOne, -amountOut.toInt256(), @@ -219,7 +223,10 @@ contract SwapRouter is params.amountOut, params.recipient, params.limitSqrtPrice, - SwapCallbackData({path: abi.encodePacked(params.tokenOut, params.tokenIn), payer: msg.sender}) + SwapCallbackData({ + path: abi.encodePacked(params.tokenOut, params.deployer, params.tokenIn), + payer: msg.sender + }) ); require(amountIn <= params.amountInMaximum, 'Too much requested'); @@ -232,6 +239,7 @@ contract SwapRouter is ) external payable override checkDeadline(params.deadline) returns (uint256 amountIn) { // it's okay that the payer is fixed to msg.sender here, as they're only paying for the "final" exact output // swap, which happens first, and subsequent swaps are paid for within nested callback frames + exactOutputInternal( params.amountOut, params.recipient, diff --git a/src/periphery/contracts/V3Migrator.sol b/src/periphery/contracts/V3Migrator.sol index 248acef4f..8089e46d2 100644 --- a/src/periphery/contracts/V3Migrator.sol +++ b/src/periphery/contracts/V3Migrator.sol @@ -57,6 +57,7 @@ contract V3Migrator is IV3Migrator, PeripheryImmutableState, PoolInitializer, Mu INonfungiblePositionManager.MintParams({ token0: params.token0, token1: params.token1, + deployer: params.deployer, tickLower: params.tickLower, tickUpper: params.tickUpper, amount0Desired: amount0V2ToMigrate, diff --git a/src/periphery/contracts/base/ERC721Permit.sol b/src/periphery/contracts/base/ERC721Permit.sol index 8467f882b..9d61fac67 100644 --- a/src/periphery/contracts/base/ERC721Permit.sol +++ b/src/periphery/contracts/base/ERC721Permit.sol @@ -73,7 +73,7 @@ abstract contract ERC721Permit is BlockTimestamp, ERC721Enumerable, IERC721Permi ) ); address owner = ownerOf(tokenId); - require(spender != owner, 'ERC721Permit: approval to current owner'); + require(spender != owner, 'Approval to current owner'); if (Address.isContract(owner)) { _checkAuthorization(IERC1271(owner).isValidSignature(digest, abi.encodePacked(r, s, v)) == 0x1626ba7e); diff --git a/src/periphery/contracts/base/LiquidityManagement.sol b/src/periphery/contracts/base/LiquidityManagement.sol index 683255dde..532a71375 100644 --- a/src/periphery/contracts/base/LiquidityManagement.sol +++ b/src/periphery/contracts/base/LiquidityManagement.sol @@ -37,6 +37,7 @@ abstract contract LiquidityManagement is IAlgebraMintCallback, PeripheryImmutabl struct AddLiquidityParams { address token0; address token1; + address deployer; address recipient; int24 tickLower; int24 tickUpper; @@ -53,7 +54,11 @@ abstract contract LiquidityManagement is IAlgebraMintCallback, PeripheryImmutabl internal returns (uint128 liquidity, uint128 actualLiquidity, uint256 amount0, uint256 amount1, IAlgebraPool pool) { - PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({token0: params.token0, token1: params.token1}); + PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({ + deployer: params.deployer, + token0: params.token0, + token1: params.token1 + }); pool = IAlgebraPool(PoolAddress.computeAddress(poolDeployer, poolKey)); diff --git a/src/periphery/contracts/base/PoolInitializer.sol b/src/periphery/contracts/base/PoolInitializer.sol index 19eb0b161..377f4928b 100644 --- a/src/periphery/contracts/base/PoolInitializer.sol +++ b/src/periphery/contracts/base/PoolInitializer.sol @@ -18,16 +18,24 @@ abstract contract PoolInitializer is IPoolInitializer, PeripheryImmutableState { function createAndInitializePoolIfNecessary( address token0, address token1, + address deployer, uint160 sqrtPriceX96 ) external payable override returns (address pool) { require(token0 < token1, 'Invalid order of tokens'); - pool = IAlgebraFactory(factory).poolByPair(token0, token1); + IAlgebraFactory _factory = IAlgebraFactory(factory); + + if (deployer == address(0)) pool = _factory.poolByPair(token0, token1); + else { + pool = _factory.customPoolByPair(deployer, token0, token1); + } if (pool == address(0)) { - pool = IAlgebraFactory(factory).createPool(token0, token1); + if (deployer == address(0)) { + pool = _factory.createPool(token0, token1); - _initializePool(pool, sqrtPriceX96); + _initializePool(pool, sqrtPriceX96); + } } else { uint160 sqrtPriceX96Existing = IAlgebraPool(pool)._getSqrtPrice(); if (sqrtPriceX96Existing == 0) { diff --git a/src/periphery/contracts/interfaces/INonfungiblePositionManager.sol b/src/periphery/contracts/interfaces/INonfungiblePositionManager.sol index 7f962b696..43d01cbed 100644 --- a/src/periphery/contracts/interfaces/INonfungiblePositionManager.sol +++ b/src/periphery/contracts/interfaces/INonfungiblePositionManager.sol @@ -60,10 +60,6 @@ interface INonfungiblePositionManager is /// @param tokenId The ID of corresponding token event FarmingFailed(uint256 indexed tokenId); - /// @notice Emitted after farming center address change - /// @param farmingCenterAddress The new address of connected farming center - event FarmingCenter(address farmingCenterAddress); - /// @notice Returns the position information associated with a given token ID. /// @dev Throws if the token ID is not valid. /// @param tokenId The ID of the token that represents the position @@ -71,6 +67,7 @@ interface INonfungiblePositionManager is /// @return operator The address that is approved for spending /// @return token0 The address of the token0 for a specific pool /// @return token1 The address of the token1 for a specific pool + /// @return deployer The address of the custom pool deployer /// @return tickLower The lower end of the tick range for the position /// @return tickUpper The higher end of the tick range for the position /// @return liquidity The liquidity of the position @@ -88,6 +85,7 @@ interface INonfungiblePositionManager is address operator, address token0, address token1, + address deployer, int24 tickLower, int24 tickUpper, uint128 liquidity, @@ -100,6 +98,7 @@ interface INonfungiblePositionManager is struct MintParams { address token0; address token1; + address deployer; int24 tickLower; int24 tickUpper; uint256 amount0Desired; diff --git a/src/periphery/contracts/interfaces/IPoolInitializer.sol b/src/periphery/contracts/interfaces/IPoolInitializer.sol index eb438c457..a8f8aac5b 100644 --- a/src/periphery/contracts/interfaces/IPoolInitializer.sol +++ b/src/periphery/contracts/interfaces/IPoolInitializer.sol @@ -17,6 +17,7 @@ interface IPoolInitializer { function createAndInitializePoolIfNecessary( address token0, address token1, + address deployer, uint160 sqrtPriceX96 ) external payable returns (address pool); } diff --git a/src/periphery/contracts/interfaces/IQuoter.sol b/src/periphery/contracts/interfaces/IQuoter.sol index 3002e54e3..8282c6b37 100644 --- a/src/periphery/contracts/interfaces/IQuoter.sol +++ b/src/periphery/contracts/interfaces/IQuoter.sol @@ -13,9 +13,10 @@ interface IQuoter { /// @param path The path of the swap, i.e. each token pair /// @param amountIn The amount of the first token to swap /// @return amountOut The amount of the last token that would be received - function quoteExactInput(bytes memory path, uint256 amountIn) - external - returns (uint256 amountOut, uint16[] memory fees); + function quoteExactInput( + bytes memory path, + uint256 amountIn + ) external returns (uint256 amountOut, uint16[] memory fees); /// @notice Returns the amount out received for a given exact input but for a swap of a single pool /// @param tokenIn The token being swapped in @@ -26,6 +27,7 @@ interface IQuoter { function quoteExactInputSingle( address tokenIn, address tokenOut, + address deployer, uint256 amountIn, uint160 limitSqrtPrice ) external returns (uint256 amountOut, uint16 fee); @@ -34,9 +36,10 @@ interface IQuoter { /// @param path The path of the swap, i.e. each token pair. Path must be provided in reverse order /// @param amountOut The amount of the last token to receive /// @return amountIn The amount of first token required to be paid - function quoteExactOutput(bytes memory path, uint256 amountOut) - external - returns (uint256 amountIn, uint16[] memory fees); + function quoteExactOutput( + bytes memory path, + uint256 amountOut + ) external returns (uint256 amountIn, uint16[] memory fees); /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool /// @param tokenIn The token being swapped in @@ -47,6 +50,7 @@ interface IQuoter { function quoteExactOutputSingle( address tokenIn, address tokenOut, + address deployer, uint256 amountOut, uint160 limitSqrtPrice ) external returns (uint256 amountIn, uint16 fee); diff --git a/src/periphery/contracts/interfaces/IQuoterV2.sol b/src/periphery/contracts/interfaces/IQuoterV2.sol index fc0d48c8a..4782649ab 100644 --- a/src/periphery/contracts/interfaces/IQuoterV2.sol +++ b/src/periphery/contracts/interfaces/IQuoterV2.sol @@ -36,6 +36,7 @@ interface IQuoterV2 { struct QuoteExactInputSingleParams { address tokenIn; address tokenOut; + address deployer; uint256 amountIn; uint160 limitSqrtPrice; } @@ -91,6 +92,7 @@ interface IQuoterV2 { struct QuoteExactOutputSingleParams { address tokenIn; address tokenOut; + address deployer; uint256 amount; uint160 limitSqrtPrice; } diff --git a/src/periphery/contracts/interfaces/ISwapRouter.sol b/src/periphery/contracts/interfaces/ISwapRouter.sol index 17307cb24..8bc91702d 100644 --- a/src/periphery/contracts/interfaces/ISwapRouter.sol +++ b/src/periphery/contracts/interfaces/ISwapRouter.sol @@ -12,6 +12,7 @@ interface ISwapRouter is IAlgebraSwapCallback { struct ExactInputSingleParams { address tokenIn; address tokenOut; + address deployer; address recipient; uint256 deadline; uint256 amountIn; @@ -40,6 +41,7 @@ interface ISwapRouter is IAlgebraSwapCallback { struct ExactOutputSingleParams { address tokenIn; address tokenOut; + address deployer; address recipient; uint256 deadline; uint256 amountOut; diff --git a/src/periphery/contracts/interfaces/IV3Migrator.sol b/src/periphery/contracts/interfaces/IV3Migrator.sol index d7be09881..665de349c 100644 --- a/src/periphery/contracts/interfaces/IV3Migrator.sol +++ b/src/periphery/contracts/interfaces/IV3Migrator.sol @@ -17,6 +17,7 @@ interface IV3Migrator is IMulticall, ISelfPermit, IPoolInitializer { uint8 percentageToMigrate; // represented as a numerator over 100 address token0; address token1; + address deployer; int24 tickLower; int24 tickUpper; uint256 amount0Min; // must be discounted by percentageToMigrate diff --git a/src/periphery/contracts/lens/Quoter.sol b/src/periphery/contracts/lens/Quoter.sol index 44fe4efeb..ade42f985 100644 --- a/src/periphery/contracts/lens/Quoter.sol +++ b/src/periphery/contracts/lens/Quoter.sol @@ -33,21 +33,21 @@ contract Quoter is IQuoter, IAlgebraSwapCallback, PeripheryImmutableState { address _poolDeployer ) PeripheryImmutableState(_factory, _WNativeToken, _poolDeployer) {} - function getPool(address tokenA, address tokenB) private view returns (IAlgebraPool) { - return IAlgebraPool(PoolAddress.computeAddress(poolDeployer, PoolAddress.getPoolKey(tokenA, tokenB))); + function getPool(address deployer, address tokenA, address tokenB) private view returns (IAlgebraPool) { + return IAlgebraPool(PoolAddress.computeAddress(poolDeployer, PoolAddress.getPoolKey(deployer, tokenA, tokenB))); } /// @inheritdoc IAlgebraSwapCallback function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes memory path) external view override { require(amount0Delta > 0 || amount1Delta > 0, 'Zero liquidity swap'); // swaps entirely within 0-liquidity regions are not supported - (address tokenIn, address tokenOut) = path.decodeFirstPool(); - CallbackValidation.verifyCallback(poolDeployer, tokenIn, tokenOut); + (address tokenIn, address deployer, address tokenOut) = path.decodeFirstPool(); + CallbackValidation.verifyCallback(poolDeployer, deployer, tokenIn, tokenOut); (bool isExactInput, uint256 amountToPay, uint256 amountReceived) = amount0Delta > 0 ? (tokenIn < tokenOut, uint256(amount0Delta), uint256(-amount1Delta)) : (tokenOut < tokenIn, uint256(amount1Delta), uint256(-amount0Delta)); - IAlgebraPool pool = getPool(tokenIn, tokenOut); + IAlgebraPool pool = getPool(deployer, tokenIn, tokenOut); (, , uint16 fee, , , ) = pool.globalState(); if (isExactInput) { @@ -84,20 +84,21 @@ contract Quoter is IQuoter, IAlgebraSwapCallback, PeripheryImmutableState { function quoteExactInputSingle( address tokenIn, address tokenOut, + address deployer, uint256 amountIn, uint160 limitSqrtPrice ) public override returns (uint256 amountOut, uint16 fee) { bool zeroToOne = tokenIn < tokenOut; try - getPool(tokenIn, tokenOut).swap( + getPool(deployer, tokenIn, tokenOut).swap( address(this), // address(0) might cause issues with some tokens zeroToOne, amountIn.toInt256(), limitSqrtPrice == 0 ? (zeroToOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) : limitSqrtPrice, - abi.encodePacked(tokenIn, tokenOut) + abi.encodePacked(tokenIn, deployer, tokenOut) ) {} catch (bytes memory reason) { (amountOut, fee) = parseRevertReason(reason); @@ -114,10 +115,10 @@ contract Quoter is IQuoter, IAlgebraSwapCallback, PeripheryImmutableState { while (true) { bool hasMultiplePools = path.hasMultiplePools(); - (address tokenIn, address tokenOut) = path.decodeFirstPool(); + (address tokenIn, address deployer, address tokenOut) = path.decodeFirstPool(); // the outputs of prior swaps become the inputs to subsequent ones - (amountIn, fees[i]) = quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); + (amountIn, fees[i]) = quoteExactInputSingle(tokenIn, tokenOut, deployer, amountIn, 0); // decide whether to continue or terminate if (hasMultiplePools) { @@ -133,6 +134,7 @@ contract Quoter is IQuoter, IAlgebraSwapCallback, PeripheryImmutableState { function quoteExactOutputSingle( address tokenIn, address tokenOut, + address deployer, uint256 amountOut, uint160 limitSqrtPrice ) public override returns (uint256 amountIn, uint16 fee) { @@ -141,14 +143,14 @@ contract Quoter is IQuoter, IAlgebraSwapCallback, PeripheryImmutableState { // if no price limit has been specified, cache the output amount for comparison in the swap callback if (limitSqrtPrice == 0) amountOutCached = amountOut; try - getPool(tokenIn, tokenOut).swap( + getPool(deployer, tokenIn, tokenOut).swap( address(this), // address(0) might cause issues with some tokens zeroToOne, -amountOut.toInt256(), limitSqrtPrice == 0 ? (zeroToOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) : limitSqrtPrice, - abi.encodePacked(tokenOut, tokenIn) + abi.encodePacked(tokenOut, deployer, tokenIn) ) {} catch (bytes memory reason) { if (limitSqrtPrice == 0) delete amountOutCached; // clear cache @@ -166,10 +168,10 @@ contract Quoter is IQuoter, IAlgebraSwapCallback, PeripheryImmutableState { while (true) { bool hasMultiplePools = path.hasMultiplePools(); - (address tokenOut, address tokenIn) = path.decodeFirstPool(); + (address tokenOut, address deployer, address tokenIn) = path.decodeFirstPool(); // the inputs of prior swaps become the outputs of subsequent ones - (amountOut, fees[i]) = quoteExactOutputSingle(tokenIn, tokenOut, amountOut, 0); + (amountOut, fees[i]) = quoteExactOutputSingle(tokenIn, tokenOut, deployer, amountOut, 0); // decide whether to continue or terminate if (hasMultiplePools) { diff --git a/src/periphery/contracts/lens/QuoterV2.sol b/src/periphery/contracts/lens/QuoterV2.sol index bf0a29f5b..8edf893ae 100644 --- a/src/periphery/contracts/lens/QuoterV2.sol +++ b/src/periphery/contracts/lens/QuoterV2.sol @@ -33,21 +33,21 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { address _poolDeployer ) PeripheryImmutableState(_factory, _WNativeToken, _poolDeployer) {} - function getPool(address tokenA, address tokenB) private view returns (IAlgebraPool) { - return IAlgebraPool(PoolAddress.computeAddress(poolDeployer, PoolAddress.getPoolKey(tokenA, tokenB))); + function getPool(address deployer, address tokenA, address tokenB) private view returns (IAlgebraPool) { + return IAlgebraPool(PoolAddress.computeAddress(poolDeployer, PoolAddress.getPoolKey(deployer, tokenA, tokenB))); } /// @inheritdoc IAlgebraSwapCallback function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes memory path) external view override { require(amount0Delta > 0 || amount1Delta > 0, 'Zero liquidity swap'); // swaps entirely within 0-liquidity regions are not supported - (address tokenIn, address tokenOut) = path.decodeFirstPool(); - CallbackValidation.verifyCallback(poolDeployer, tokenIn, tokenOut); + (address tokenIn, address deployer, address tokenOut) = path.decodeFirstPool(); + CallbackValidation.verifyCallback(poolDeployer, deployer, tokenIn, tokenOut); (bool isExactInput, uint256 amountToPay, uint256 amountReceived) = amount0Delta > 0 ? (tokenIn < tokenOut, uint256(amount0Delta), uint256(-amount1Delta)) : (tokenOut < tokenIn, uint256(amount1Delta), uint256(-amount0Delta)); - IAlgebraPool pool = getPool(tokenIn, tokenOut); + IAlgebraPool pool = getPool(deployer, tokenIn, tokenOut); (uint160 sqrtPriceX96After, int24 tickAfter, uint16 fee, , , ) = pool.globalState(); if (isExactInput) { @@ -133,10 +133,10 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { ) { bool zeroToOne = params.tokenIn < params.tokenOut; - IAlgebraPool pool = getPool(params.tokenIn, params.tokenOut); + IAlgebraPool pool = getPool(params.deployer, params.tokenIn, params.tokenOut); uint256 gasBefore = gasleft(); - bytes memory data = abi.encodePacked(params.tokenIn, params.tokenOut); + bytes memory data = abi.encodePacked(params.tokenIn, params.deployer, params.tokenOut); try pool.swap( address(this), // address(0) might cause issues with some tokens @@ -176,9 +176,10 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { while (true) { QuoteExactInputSingleParams memory params; { - (address tokenIn, address tokenOut) = path.decodeFirstPool(); + (address tokenIn, address deployer, address tokenOut) = path.decodeFirstPool(); params.tokenIn = tokenIn; + params.deployer = deployer; params.tokenOut = tokenOut; params.amountIn = amountInRequired; } @@ -233,12 +234,12 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { ) { bool zeroToOne = params.tokenIn < params.tokenOut; - IAlgebraPool pool = getPool(params.tokenIn, params.tokenOut); + IAlgebraPool pool = getPool(params.deployer, params.tokenIn, params.tokenOut); // if no price limit has been specified, cache the output amount for comparison in the swap callback if (params.limitSqrtPrice == 0) amountOutCached = params.amount; uint256 gasBefore = gasleft(); - bytes memory data = abi.encodePacked(params.tokenOut, params.tokenIn); + bytes memory data = abi.encodePacked(params.tokenOut, params.deployer, params.tokenIn); try pool.swap( address(this), // address(0) might cause issues with some tokens @@ -279,9 +280,10 @@ contract QuoterV2 is IQuoterV2, IAlgebraSwapCallback, PeripheryImmutableState { while (true) { QuoteExactOutputSingleParams memory params; { - (address tokenOut, address tokenIn) = path.decodeFirstPool(); + (address tokenOut, address deployer, address tokenIn) = path.decodeFirstPool(); params.tokenIn = tokenIn; + params.deployer = deployer; params.tokenOut = tokenOut; params.amount = amountOutRequired; } diff --git a/src/periphery/contracts/libraries/CallbackValidation.sol b/src/periphery/contracts/libraries/CallbackValidation.sol index 8d239dc63..837c0318a 100644 --- a/src/periphery/contracts/libraries/CallbackValidation.sol +++ b/src/periphery/contracts/libraries/CallbackValidation.sol @@ -10,15 +10,17 @@ import './PoolAddress.sol'; library CallbackValidation { /// @notice Returns the address of a valid Algebra Pool /// @param poolDeployer The contract address of the Algebra pool deployer + /// @param deployer The custom pool deployer address /// @param tokenA The contract address of either token0 or token1 /// @param tokenB The contract address of the other token /// @return pool The Algebra pool contract address function verifyCallback( address poolDeployer, + address deployer, address tokenA, address tokenB ) internal view returns (IAlgebraPool pool) { - return verifyCallback(poolDeployer, PoolAddress.getPoolKey(tokenA, tokenB)); + return verifyCallback(poolDeployer, PoolAddress.getPoolKey(deployer, tokenA, tokenB)); } /// @notice Returns the address of a valid Algebra Pool diff --git a/src/periphery/contracts/libraries/Path.sol b/src/periphery/contracts/libraries/Path.sol index 3855441c8..df5b59ca5 100644 --- a/src/periphery/contracts/libraries/Path.sol +++ b/src/periphery/contracts/libraries/Path.sol @@ -12,8 +12,10 @@ library Path { /// @dev The length of the bytes encoded address uint256 private constant ADDR_SIZE = 20; - /// @dev The offset of a single token address - uint256 private constant NEXT_OFFSET = ADDR_SIZE; + /// @dev The offset of a custom pool deployer address + uint256 private constant DEPLOYER_OFFSET = ADDR_SIZE; + /// @dev The offset of a single token address + deployer address + uint256 private constant NEXT_OFFSET = ADDR_SIZE + DEPLOYER_OFFSET; /// @dev The offset of an encoded pool key uint256 private constant POP_OFFSET = NEXT_OFFSET + ADDR_SIZE; /// @dev The minimum length of an encoding that contains 2 or more pools @@ -37,9 +39,13 @@ library Path { /// @notice Decodes the first pool in path /// @param path The bytes encoded swap path /// @return tokenA The first token of the given pool + /// @return deployer The address of the custom pool deployer /// @return tokenB The second token of the given pool - function decodeFirstPool(bytes memory path) internal pure returns (address tokenA, address tokenB) { + function decodeFirstPool( + bytes memory path + ) internal pure returns (address tokenA, address deployer, address tokenB) { tokenA = path.toAddress(0); + deployer = path.toAddress(DEPLOYER_OFFSET); tokenB = path.toAddress(NEXT_OFFSET); } diff --git a/src/periphery/contracts/libraries/PoolAddress.sol b/src/periphery/contracts/libraries/PoolAddress.sol index 10bb5661f..387afa58b 100644 --- a/src/periphery/contracts/libraries/PoolAddress.sol +++ b/src/periphery/contracts/libraries/PoolAddress.sol @@ -9,17 +9,19 @@ library PoolAddress { /// @notice The identifying key of the pool struct PoolKey { + address deployer; address token0; address token1; } /// @notice Returns PoolKey: the ordered tokens + /// @param deployer The custom pool deployer address /// @param tokenA The first token of a pool, unsorted /// @param tokenB The second token of a pool, unsorted /// @return Poolkey The pool details with ordered token0 and token1 assignments - function getPoolKey(address tokenA, address tokenB) internal pure returns (PoolKey memory) { + function getPoolKey(address deployer, address tokenA, address tokenB) internal pure returns (PoolKey memory) { if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); - return PoolKey({token0: tokenA, token1: tokenB}); + return PoolKey({deployer: deployer, token0: tokenA, token1: tokenB}); } /// @notice Deterministically computes the pool address given the poolDeployer and PoolKey @@ -35,7 +37,11 @@ library PoolAddress { abi.encodePacked( hex'ff', poolDeployer, - keccak256(abi.encode(key.token0, key.token1)), + keccak256( + key.deployer == address(0) + ? abi.encode(key.token0, key.token1) + : abi.encode(key.deployer, key.token0, key.token1) + ), POOL_INIT_CODE_HASH ) ) diff --git a/src/periphery/contracts/libraries/PoolInteraction.sol b/src/periphery/contracts/libraries/PoolInteraction.sol index e9b9e2b7d..7d18f1e70 100644 --- a/src/periphery/contracts/libraries/PoolInteraction.sol +++ b/src/periphery/contracts/libraries/PoolInteraction.sol @@ -23,8 +23,7 @@ library PoolInteraction { uint128 fees1 ) { - bytes32 positionKey = PositionKey.compute(owner, tickLower, tickUpper); - return pool.positions(positionKey); + return pool.positions(PositionKey.compute(owner, tickLower, tickUpper)); } function _getSqrtPrice(IAlgebraPool pool) internal view returns (uint160 sqrtPriceX96) { diff --git a/src/periphery/contracts/libraries/PositionValue.sol b/src/periphery/contracts/libraries/PositionValue.sol index 3866b779c..327272919 100644 --- a/src/periphery/contracts/libraries/PositionValue.sol +++ b/src/periphery/contracts/libraries/PositionValue.sol @@ -15,6 +15,7 @@ library PositionValue { struct PositionCache { address token0; address token1; + address deployer; int24 tickLower; int24 tickUpper; uint128 liquidity; @@ -59,7 +60,7 @@ library PositionValue { uint256 tokenId, uint160 sqrtRatioX96 ) internal view returns (uint256 amount0, uint256 amount1) { - (, , , , int24 tickLower, int24 tickUpper, uint128 liquidity, , , , ) = positionManager.positions(tokenId); + (, , , , , int24 tickLower, int24 tickUpper, uint128 liquidity, , , , ) = positionManager.positions(tokenId); return _principal(sqrtRatioX96, tickLower, tickUpper, liquidity); } @@ -100,7 +101,11 @@ library PositionValue { IAlgebraPool( PoolAddress.computeAddress( positionManager.poolDeployer(), - PoolAddress.PoolKey({token0: position.token0, token1: position.token1}) + PoolAddress.PoolKey({ + deployer: position.deployer, + token0: position.token0, + token1: position.token1 + }) ) ), position.tickLower, @@ -134,6 +139,7 @@ library PositionValue { , address token0, address token1, + address deployer, int24 tickLower, int24 tickUpper, uint128 liquidity, @@ -147,6 +153,7 @@ library PositionValue { PositionCache( token0, token1, + deployer, tickLower, tickUpper, liquidity, diff --git a/src/periphery/contracts/test/CustomPlugin.sol b/src/periphery/contracts/test/CustomPlugin.sol new file mode 100644 index 000000000..0b97b6921 --- /dev/null +++ b/src/periphery/contracts/test/CustomPlugin.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.20; + +import '@cryptoalgebra/integral-core/contracts/base/common/Timestamp.sol'; +import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol'; + +import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol'; +import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol'; +import '@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolState.sol'; +import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol'; + +contract CustomPlugin is Timestamp, IAlgebraPlugin { + using Plugins for uint8; + + address public pool; + bytes32 public constant ALGEBRA_BASE_PLUGIN_MANAGER = keccak256('ALGEBRA_BASE_PLUGIN_MANAGER'); + + function _getPoolState() internal view returns (uint160 price, int24 tick, uint16 fee, uint8 pluginConfig) { + (price, tick, fee, pluginConfig, , ) = IAlgebraPoolState(pool).globalState(); + } + + /// @inheritdoc IAlgebraPlugin + uint8 public constant override defaultPluginConfig = + uint8(Plugins.BEFORE_SWAP_FLAG | Plugins.AFTER_SWAP_FLAG | Plugins.DYNAMIC_FEE); + + function beforeInitialize(address, uint160) external override returns (bytes4) { + pool = msg.sender; + _updatePluginConfigInPool(); + return IAlgebraPlugin.beforeInitialize.selector; + } + + function afterInitialize(address, uint160, int24) external override returns (bytes4) { + _updatePluginConfigInPool(); + return IAlgebraPlugin.afterInitialize.selector; + } + + /// @dev unused + function beforeModifyPosition( + address, + address, + int24, + int24, + int128, + bytes calldata + ) external override returns (bytes4) { + _updatePluginConfigInPool(); // should not be called, reset config + return IAlgebraPlugin.beforeModifyPosition.selector; + } + + /// @dev unused + function afterModifyPosition( + address, + address, + int24, + int24, + int128, + uint256, + uint256, + bytes calldata + ) external override returns (bytes4) { + _updatePluginConfigInPool(); // should not be called, reset config + return IAlgebraPlugin.afterModifyPosition.selector; + } + + function beforeSwap( + address, + address, + bool, + int256, + uint160, + bool, + bytes calldata + ) external override returns (bytes4) { + IAlgebraPool(pool).setFee(10000); + return IAlgebraPlugin.beforeSwap.selector; + } + + function afterSwap( + address, + address, + bool, + int256, + uint160, + int256, + int256, + bytes calldata + ) external override returns (bytes4) { + IAlgebraPool(pool).setFee(100); + return IAlgebraPlugin.afterSwap.selector; + } + + /// @dev unused + function beforeFlash(address, address, uint256, uint256, bytes calldata) external override returns (bytes4) { + _updatePluginConfigInPool(); // should not be called, reset config + return IAlgebraPlugin.beforeFlash.selector; + } + + /// @dev unused + function afterFlash( + address, + address, + uint256, + uint256, + uint256, + uint256, + bytes calldata + ) external override returns (bytes4) { + _updatePluginConfigInPool(); // should not be called, reset config + return IAlgebraPlugin.afterFlash.selector; + } + + function _updatePluginConfigInPool() internal { + uint8 newPluginConfig = defaultPluginConfig; + + (, , , uint8 currentPluginConfig) = _getPoolState(); + if (currentPluginConfig != newPluginConfig) { + IAlgebraPool(pool).setPluginConfig(newPluginConfig); + } + } +} diff --git a/src/periphery/contracts/test/CustomPoolDeployerTest.sol b/src/periphery/contracts/test/CustomPoolDeployerTest.sol index 6a0812bdb..6dc625a56 100644 --- a/src/periphery/contracts/test/CustomPoolDeployerTest.sol +++ b/src/periphery/contracts/test/CustomPoolDeployerTest.sol @@ -5,11 +5,13 @@ import {IAlgebraCustomPoolEntryPoint} from '../interfaces/IAlgebraCustomPoolEntr contract CustomPoolDeployerTest { address public immutable entryPoint; + address public immutable plugin; mapping(address => address) public poolToPlugin; - constructor(address _entryPoint) { + constructor(address _entryPoint, address _plugin) { entryPoint = _entryPoint; + plugin = _plugin; } function createCustomPool( @@ -23,7 +25,7 @@ contract CustomPoolDeployerTest { } function beforeCreatePoolHook( - address pool, + address, address, address, address, @@ -32,15 +34,15 @@ contract CustomPoolDeployerTest { ) external view returns (address) { require(msg.sender == entryPoint, 'Only entryPoint'); - return poolToPlugin[pool]; + return plugin; } function afterCreatePoolHook(address, address, address) external pure { return; } - function setPluginForPool(address pool, address plugin) external { - poolToPlugin[pool] = plugin; + function setPluginForPool(address pool, address _plugin) external { + poolToPlugin[pool] = _plugin; } function setTickSpacing(address pool, int24 newTickSpacing) external { diff --git a/src/periphery/contracts/test/MockPlugin.sol b/src/periphery/contracts/test/MockPlugin.sol new file mode 100644 index 000000000..48e1a1621 --- /dev/null +++ b/src/periphery/contracts/test/MockPlugin.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.20; + +import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPlugin.sol'; + +contract MockPlugin is IAlgebraPlugin { + function defaultPluginConfig() external pure returns (uint8) { + return 0; + } + + function beforeInitialize(address, uint160) external pure returns (bytes4) { + return IAlgebraPlugin.beforeInitialize.selector; + } + + function afterInitialize(address, uint160, int24) external pure returns (bytes4) { + return IAlgebraPlugin.afterInitialize.selector; + } + + function beforeModifyPosition( + address, + address, + int24, + int24, + int128, + bytes calldata + ) external pure returns (bytes4) { + return IAlgebraPlugin.beforeModifyPosition.selector; + } + + function afterModifyPosition( + address, + address, + int24, + int24, + int128, + uint256, + uint256, + bytes calldata + ) external pure returns (bytes4) { + return IAlgebraPlugin.afterModifyPosition.selector; + } + + function beforeSwap(address, address, bool, int256, uint160, bool, bytes calldata) external pure returns (bytes4) { + return IAlgebraPlugin.beforeSwap.selector; + } + + function afterSwap( + address, + address, + bool, + int256, + uint160, + int256, + int256, + bytes calldata + ) external pure returns (bytes4) { + return IAlgebraPlugin.afterSwap.selector; + } + + function beforeFlash(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { + return IAlgebraPlugin.beforeFlash.selector; + } + + function afterFlash( + address, + address, + uint256, + uint256, + uint256, + uint256, + bytes calldata + ) external pure returns (bytes4) { + return IAlgebraPlugin.afterFlash.selector; + } +} diff --git a/src/periphery/contracts/test/MockPluginFactory.sol b/src/periphery/contracts/test/MockPluginFactory.sol new file mode 100644 index 000000000..ff6bc7039 --- /dev/null +++ b/src/periphery/contracts/test/MockPluginFactory.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.20; + +import './MockPlugin.sol'; + +import '@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol'; +import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol'; + +contract MockPluginFactory is IAlgebraPluginFactory { + address public immutable algebraFactory; + + mapping(address poolAddress => address pluginAddress) public pluginByPool; + + 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); + } + + // 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'); + MockPlugin mockPlugin = new MockPlugin(); + pluginByPool[pool] = address(mockPlugin); + return address(mockPlugin); + } +} diff --git a/src/periphery/contracts/test/PathTest.sol b/src/periphery/contracts/test/PathTest.sol index c6243b9bb..435418148 100644 --- a/src/periphery/contracts/test/PathTest.sol +++ b/src/periphery/contracts/test/PathTest.sol @@ -8,7 +8,7 @@ contract PathTest { return Path.hasMultiplePools(path); } - function decodeFirstPool(bytes memory path) public pure returns (address tokenA, address tokenB) { + function decodeFirstPool(bytes memory path) public pure returns (address tokenA, address deployer, address tokenB) { return Path.decodeFirstPool(path); } diff --git a/src/periphery/contracts/test/PoolAddressTest.sol b/src/periphery/contracts/test/PoolAddressTest.sol index 0da477616..7539bd6fd 100644 --- a/src/periphery/contracts/test/PoolAddressTest.sol +++ b/src/periphery/contracts/test/PoolAddressTest.sol @@ -12,21 +12,27 @@ contract PoolAddressTest { address factory, address token0, address token1, + address deployer, uint24 fee ) external pure returns (address) { fee; - return PoolAddress.computeAddress(factory, PoolAddress.PoolKey({token0: token0, token1: token1})); + return + PoolAddress.computeAddress( + factory, + PoolAddress.PoolKey({deployer: deployer, token0: token0, token1: token1}) + ); } function getGasCostOfComputeAddress( address factory, address token0, address token1, + address deployer, uint24 fee ) external view returns (uint256) { fee; uint256 gasBefore = gasleft(); - PoolAddress.computeAddress(factory, PoolAddress.PoolKey({token0: token0, token1: token1})); + PoolAddress.computeAddress(factory, PoolAddress.PoolKey({deployer: deployer, token0: token0, token1: token1})); return gasBefore - gasleft(); } } diff --git a/src/periphery/contracts/test/TestCallbackValidation.sol b/src/periphery/contracts/test/TestCallbackValidation.sol index 28632daee..7cacdb6d9 100644 --- a/src/periphery/contracts/test/TestCallbackValidation.sol +++ b/src/periphery/contracts/test/TestCallbackValidation.sol @@ -4,7 +4,12 @@ pragma solidity =0.8.20; import '../libraries/CallbackValidation.sol'; contract TestCallbackValidation { - function verifyCallback(address factory, address tokenA, address tokenB) external view returns (IAlgebraPool pool) { - return CallbackValidation.verifyCallback(factory, tokenA, tokenB); + function verifyCallback( + address factory, + address tokenA, + address tokenB, + address deployer + ) external view returns (IAlgebraPool pool) { + return CallbackValidation.verifyCallback(factory, tokenA, tokenB, deployer); } } diff --git a/src/periphery/hardhat.config.ts b/src/periphery/hardhat.config.ts index f02552f23..bab1d6711 100644 --- a/src/periphery/hardhat.config.ts +++ b/src/periphery/hardhat.config.ts @@ -19,6 +19,20 @@ const LOW_OPTIMIZER_COMPILER_SETTINGS = { }, }; +const ZERO_RUNS_OPTIMIZER_COMPILER_SETTINGS = { + version: '0.8.20', + settings: { + evmVersion: 'paris', + optimizer: { + enabled: true, + runs: 0, + }, + metadata: { + bytecodeHash: 'none', + }, + }, +}; + const LOWEST_OPTIMIZER_COMPILER_SETTINGS = { version: '0.8.20', settings: { @@ -69,8 +83,8 @@ export default { solidity: { compilers: [DEFAULT_COMPILER_SETTINGS], overrides: { - 'contracts/NonfungiblePositionManager.sol': LOW_OPTIMIZER_COMPILER_SETTINGS, - 'contracts/test/MockTimeNonfungiblePositionManager.sol': LOW_OPTIMIZER_COMPILER_SETTINGS, + 'contracts/NonfungiblePositionManager.sol': ZERO_RUNS_OPTIMIZER_COMPILER_SETTINGS, + 'contracts/test/MockTimeNonfungiblePositionManager.sol': ZERO_RUNS_OPTIMIZER_COMPILER_SETTINGS, 'contracts/test/NFTDescriptorTest.sol': LOWEST_OPTIMIZER_COMPILER_SETTINGS, 'contracts/NonfungibleTokenPositionDescriptor.sol': LOWEST_OPTIMIZER_COMPILER_SETTINGS, 'contracts/libraries/NFTDescriptor.sol': LOWEST_OPTIMIZER_COMPILER_SETTINGS, diff --git a/src/periphery/scripts/deploy.js b/src/periphery/scripts/deploy.js index 2a3ed9fc4..8560c63a4 100644 --- a/src/periphery/scripts/deploy.js +++ b/src/periphery/scripts/deploy.js @@ -24,9 +24,11 @@ async function main() { console.log('EntryPoint deployed to:', entryPoint.target) const factory = await hre.ethers.getContractAt(AlgebraFactoryComplied.abi, deploysData.factory) - - await factory.grantRole("0xc9cf812513d9983585eb40fcfe6fd49fbb6a45815663ec33b30a6c6c7de3683b", entryPoint.target); - await factory.grantRole("0xb73ce166ead2f8e9add217713a7989e4edfba9625f71dfd2516204bb67ad3442", entryPoint.target); + + const deployerRole = await factory.grantRole("0xc9cf812513d9983585eb40fcfe6fd49fbb6a45815663ec33b30a6c6c7de3683b", entryPoint.target); + await deployerRole.wait() + const administratorRole = await factory.grantRole("0xb73ce166ead2f8e9add217713a7989e4edfba9625f71dfd2516204bb67ad3442", entryPoint.target); + await administratorRole.wait() const TickLensFactory = await hre.ethers.getContractFactory('TickLens'); const TickLens = await TickLensFactory.deploy(); diff --git a/src/periphery/scripts/deployTest.js b/src/periphery/scripts/deployTest.js new file mode 100644 index 000000000..bdd5f35e9 --- /dev/null +++ b/src/periphery/scripts/deployTest.js @@ -0,0 +1,34 @@ +const hre = require('hardhat'); +const fs = require('fs'); +const path = require('path'); +const { ethers } = require('ethers'); + +async function main() { + + const deployDataPath = path.resolve(__dirname, '../../../deploys.json'); + let deploysData = JSON.parse(fs.readFileSync(deployDataPath, 'utf8')); + + const cpFactory = await hre.ethers.getContractFactory('CustomPlugin') + const cp = await cpFactory.deploy() + + await cp.waitForDeployment() + console.log("CustomPlugin:",cp.target) + + const cpdFactory = await hre.ethers.getContractFactory('CustomPoolDeployerTest') + const cpd1 = await cpdFactory.deploy(deploysData.entryPoint, await cp.getAddress()) + + await cpd1.waitForDeployment() + console.log("Custom pool deployer with plugin:", cpd1.target) + + const cpd2 = await cpdFactory.deploy(deploysData.entryPoint,"0x0000000000000000000000000000000000000000") + + await cpd2.waitForDeployment() + console.log("Custom pool deployer without plugin",cpd2.target) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/src/periphery/test/CallbackValidation.spec.ts b/src/periphery/test/CallbackValidation.spec.ts index 739d2cc40..165d1734a 100644 --- a/src/periphery/test/CallbackValidation.spec.ts +++ b/src/periphery/test/CallbackValidation.spec.ts @@ -5,6 +5,9 @@ import completeFixture from './shared/completeFixture'; import { expect } from './shared/expect'; import { TestERC20, TestCallbackValidation } from '../typechain'; +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +export const PLUGIN_DEPLOYER_ADDRESS = '0x4d55ce59980Fb4e5C4C1c450F478587e9F52a18B'; + describe('CallbackValidation', () => { let nonpairAddr: Wallet; @@ -46,7 +49,7 @@ describe('CallbackValidation', () => { await expect( callbackValidation .connect(nonpairAddr) - .verifyCallback(await factory.poolDeployer(), await tokens[0].getAddress(), await tokens[1].getAddress()) + .verifyCallback(await factory.poolDeployer(), await tokens[0].getAddress(), await tokens[1].getAddress(), ZERO_ADDRESS) ).to.be.revertedWith('Invalid caller of callback'); }); }); diff --git a/src/periphery/test/EntryPoint.spec.ts b/src/periphery/test/EntryPoint.spec.ts index 810bf0be7..b8b10013f 100644 --- a/src/periphery/test/EntryPoint.spec.ts +++ b/src/periphery/test/EntryPoint.spec.ts @@ -18,6 +18,7 @@ import { expect } from './shared/expect'; import { encodePriceSqrt } from './shared/encodePriceSqrt'; import { abi as IAlgebraPoolABI } from '@cryptoalgebra/integral-core/artifacts/contracts/interfaces/IAlgebraPool.sol/IAlgebraPool.json'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; describe('CustomPoolEntryPoint', () => { let wallets: Wallet[]; @@ -67,7 +68,7 @@ describe('CustomPoolEntryPoint', () => { entryPoint = (await entryPointFactory.deploy(factory)) as any as AlgebraCustomPoolEntryPoint; const customPoolDeployerFactory = await ethers.getContractFactory('CustomPoolDeployerTest'); - customPoolDeployer = (await customPoolDeployerFactory.deploy(await entryPoint.getAddress())) as any as CustomPoolDeployerTest; + customPoolDeployer = (await customPoolDeployerFactory.deploy(await entryPoint.getAddress(), ZERO_ADDRESS)) as any as CustomPoolDeployerTest; let customPoolDeployerRole = await factory.CUSTOM_POOL_DEPLOYER() let poolAdministratorRole = await factory.POOLS_ADMINISTRATOR_ROLE() diff --git a/src/periphery/test/NFTDescriptor.spec.ts b/src/periphery/test/NFTDescriptor.spec.ts index d3e48654a..84a67bb5f 100644 --- a/src/periphery/test/NFTDescriptor.spec.ts +++ b/src/periphery/test/NFTDescriptor.spec.ts @@ -311,6 +311,10 @@ describe('NFTDescriptor', () => { }); it('gas [ @skip-on-coverage ]', async () => { + quoteTokenAddress = '0xabcdeabcdefabcdefabcdefabcdefabcdefabcdf'; + baseTokenAddress = '0x1234567890123456789123456789012345678901'; + quoteTokenSymbol = 'UNI'; + baseTokenSymbol = 'WNativeToken'; await snapshotGasCost( nftDescriptor.getGasCostOfConstructTokenURI({ tokenId, diff --git a/src/periphery/test/NonfungiblePositionManager.spec.ts b/src/periphery/test/NonfungiblePositionManager.spec.ts index 5bae18bfa..31d3d0dd2 100644 --- a/src/periphery/test/NonfungiblePositionManager.spec.ts +++ b/src/periphery/test/NonfungiblePositionManager.spec.ts @@ -25,6 +25,7 @@ import { sortedTokens } from './shared/tokenSort'; import { extractJSONFromURI } from './shared/extractJSONFromURI'; import { abi as IAlgebraPoolABI } from '@cryptoalgebra/integral-core/artifacts/contracts/interfaces/IAlgebraPool.sol/IAlgebraPool.json'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; describe('NonfungiblePositionManager', () => { let wallets: Wallet[]; @@ -85,13 +86,13 @@ describe('NonfungiblePositionManager', () => { ]); const code = await wallet.provider.getCode(expectedAddress); expect(code).to.eq('0x'); - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); const codeAfter = await wallet.provider.getCode(expectedAddress); expect(codeAfter).to.not.eq('0x'); }); it('is payable', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1), { value: 1 }); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1), { value: 1 }); }); it('works if pool is created but not initialized', async () => { @@ -104,7 +105,7 @@ describe('NonfungiblePositionManager', () => { await factory.createPool(tokens[0], tokens[1]); const code = await wallet.provider.getCode(expectedAddress); expect(code).to.not.eq('0x'); - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(2, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(2, 1)); }); it('works if pool is created and initialized', async () => { @@ -120,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], encodePriceSqrt(4, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(4, 1)); }); it('could theoretically use eth via multicall', async () => { @@ -128,14 +129,14 @@ describe('NonfungiblePositionManager', () => { const createAndInitializePoolIfNecessaryData = nft.interface.encodeFunctionData( 'createAndInitializePoolIfNecessary', - [await token0.getAddress(), await token1.getAddress(), encodePriceSqrt(1, 1)] + [await token0.getAddress(), await token1.getAddress(), ZERO_ADDRESS, encodePriceSqrt(1, 1)] ); await nft.multicall([createAndInitializePoolIfNecessaryData], { value: expandTo18Decimals(1) }); }); it('gas [ @skip-on-coverage ]', async () => { - await snapshotGasCost(nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1))); + await snapshotGasCost(nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1))); }); }); @@ -145,6 +146,7 @@ describe('NonfungiblePositionManager', () => { nft.mint({ token0: tokens[0], token1: tokens[1], + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), amount0Desired: 100, @@ -158,12 +160,13 @@ describe('NonfungiblePositionManager', () => { }); it('fails if cannot transfer', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); await tokens[0].approve(nft, 0); await expect( nft.mint({ token0: tokens[0], token1: tokens[1], + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), amount0Desired: 100, @@ -177,12 +180,13 @@ describe('NonfungiblePositionManager', () => { }); it('fails if deadline passed', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); await nft.setTime(2); await expect( nft.mint({ token0: tokens[0], token1: tokens[1], + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), amount0Desired: 100, @@ -199,11 +203,13 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -246,6 +252,7 @@ describe('NonfungiblePositionManager', () => { const createAndInitializeData = nft.interface.encodeFunctionData('createAndInitializePoolIfNecessary', [ await token0.getAddress(), await token1.getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1), ]); @@ -253,6 +260,7 @@ describe('NonfungiblePositionManager', () => { { token0: await token0.getAddress(), token1: await token1.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.address, @@ -284,6 +292,7 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); @@ -291,6 +300,7 @@ describe('NonfungiblePositionManager', () => { nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -305,7 +315,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, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await snapshotGasCost( nft.multicall( @@ -314,6 +324,7 @@ describe('NonfungiblePositionManager', () => { { token0: await token0.getAddress(), token1: await token1.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -333,7 +344,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, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await snapshotGasCost( nft.multicall( @@ -342,6 +353,7 @@ describe('NonfungiblePositionManager', () => { { token0: await token0.getAddress(), token1: await token1.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -360,11 +372,12 @@ describe('NonfungiblePositionManager', () => { }); it('gas mint on same ticks [ @skip-on-coverage ]', async () => { - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -379,6 +392,7 @@ describe('NonfungiblePositionManager', () => { nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -395,12 +409,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -415,6 +431,7 @@ describe('NonfungiblePositionManager', () => { nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]) + TICK_SPACINGS[FeeAmount.MEDIUM], tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]) - TICK_SPACINGS[FeeAmount.MEDIUM], recipient: wallet.address, @@ -434,12 +451,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -504,12 +523,13 @@ describe('NonfungiblePositionManager', () => { const tokenId = 1; - await nft.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); const mintData = nft.interface.encodeFunctionData('mint', [ { token0: await token0.getAddress(), token1: await token1.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.address, @@ -556,12 +576,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -644,6 +666,7 @@ describe('NonfungiblePositionManager', () => { await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -677,12 +700,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -823,12 +848,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -847,19 +874,19 @@ describe('NonfungiblePositionManager', () => { }); it('cannot be called while there is still liquidity', async () => { - await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared'); + await expect(nft.connect(other).burn(tokenId)).to.be.reverted; }); it('cannot be called while there is still partial liquidity', async () => { await nft.connect(other).decreaseLiquidity({ tokenId, liquidity: 50, amount0Min: 0, amount1Min: 0, deadline: 1 }); - await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared'); + await expect(nft.connect(other).burn(tokenId)).to.be.reverted; }); it('cannot be called while there is still tokens owed', async () => { await nft .connect(other) .decreaseLiquidity({ tokenId, liquidity: 100, amount0Min: 0, amount1Min: 0, deadline: 1 }); - await expect(nft.connect(other).burn(tokenId)).to.be.revertedWith('Not cleared'); + await expect(nft.connect(other).burn(tokenId)).to.be.reverted; }); it('deletes the token', async () => { @@ -902,12 +929,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -956,12 +985,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -1019,12 +1050,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: testPositionNFTOwner.getAddress(), @@ -1077,12 +1110,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -1164,12 +1199,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -1202,12 +1239,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); // nft 1 earns 25% of fees await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), amount0Desired: 100, @@ -1221,9 +1260,9 @@ describe('NonfungiblePositionManager', () => { await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), - amount0Desired: 300, amount1Desired: 300, amount0Min: 0, @@ -1240,7 +1279,7 @@ describe('NonfungiblePositionManager', () => { await router.exactInput({ recipient: wallet.address, deadline: 1, - path: encodePath([await tokens[0].getAddress(), await tokens[1].getAddress()]), + path: encodePath([await tokens[0].getAddress(), ZERO_ADDRESS, await tokens[1].getAddress()]), amountIn: swapAmount, amountOutMinimum: 0, }); @@ -1303,12 +1342,14 @@ describe('NonfungiblePositionManager', () => { await nft.createAndInitializePoolIfNecessary( tokens[0].getAddress(), tokens[1].getAddress(), + ZERO_ADDRESS, encodePriceSqrt(1, 1) ); await nft.mint({ token0: tokens[0].getAddress(), token1: tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: other.getAddress(), @@ -1349,9 +1390,7 @@ describe('NonfungiblePositionManager', () => { it('can not approve for invalid farming', async () => { await nft.setFarmingCenter(wallet.address); - await expect(nft.connect(other).approveForFarming(tokenId, true, nft)).to.be.revertedWith( - 'Invalid farming address' - ); + await expect(nft.connect(other).approveForFarming(tokenId, true, nft)).to.be.reverted; }); it('can revoke approval for farming', async () => { diff --git a/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts b/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts index ec91b333f..53a7b3093 100644 --- a/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts +++ b/src/periphery/test/NonfungibleTokenPositionDescriptor.spec.ts @@ -9,6 +9,7 @@ import { FeeAmount, TICK_SPACINGS, tokenAddresses } from './shared/constants'; import { getMaxTick, getMinTick } from './shared/ticks'; import { sortedTokens } from './shared/tokenSort'; import { extractJSONFromURI } from './shared/extractJSONFromURI'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address: string | undefined }; @@ -114,12 +115,13 @@ 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, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await wnative.approve(nft, 100); await tokens[1].approve(nft, 100); await nft.mint({ token0: token0, token1: token1, + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -138,12 +140,13 @@ 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, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await tokens[1].approve(nft, 100); await tokens[2].approve(nft, 100); await nft.mint({ token0: token0, token1: token1, + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, diff --git a/src/periphery/test/Path.spec.ts b/src/periphery/test/Path.spec.ts index 4669138c6..1876bfc3f 100644 --- a/src/periphery/test/Path.spec.ts +++ b/src/periphery/test/Path.spec.ts @@ -8,13 +8,16 @@ import { PathTest } from '../typechain'; import { decodePath, encodePath } from './shared/path'; import snapshotGasCost from './shared/snapshotGasCost'; +import { ZERO_ADDRESS, PLUGIN_DEPLOYER_ADDRESS } from './CallbackValidation.spec'; describe('Path', () => { let path: PathTest; let tokenAddresses = [ '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707', + ZERO_ADDRESS, '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9', + PLUGIN_DEPLOYER_ADDRESS, '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9', ]; let fees = [FeeAmount.MEDIUM, FeeAmount.MEDIUM]; @@ -55,7 +58,8 @@ describe('Path', () => { const firstPool = await path.decodeFirstPool(encodedPath); expect(firstPool.tokenA).to.be.eq(tokenAddresses[0]); - expect(firstPool.tokenB).to.be.eq(tokenAddresses[1]); + expect(firstPool.deployer).to.be.eq(tokenAddresses[1]); + expect(firstPool.tokenB).to.be.eq(tokenAddresses[2]); expect(await path.decodeFirstPool(await path.getFirstPool(encodedPath))).to.deep.eq(firstPool); }); @@ -64,17 +68,18 @@ describe('Path', () => { it('skips 1 item', async () => { const skipped = await path.skipToken(encodedPath); - expect(skipped).to.be.eq('0x' + encodedPath.slice(2 + offset * 2)); + expect(skipped).to.be.eq('0x' + encodedPath.slice(2 + offset * 4)); expect(await path.hasMultiplePools(skipped)).to.be.false; - const { tokenA, tokenB } = await path.decodeFirstPool(skipped); - expect(tokenA).to.be.eq(tokenAddresses[1]); - expect(tokenB).to.be.eq(tokenAddresses[2]); + const { tokenA, deployer, tokenB } = await path.decodeFirstPool(skipped); + expect(tokenA).to.be.eq(tokenAddresses[2]); + expect(deployer).to.be.eq(tokenAddresses[3]); + expect(tokenB).to.be.eq(tokenAddresses[4]); }); }); it('gas cost [ @skip-on-coverage ]', async () => { - await snapshotGasCost(path.getGasCostOfDecodeFirstPool(encodePath([tokenAddresses[0], tokenAddresses[1]]))); + await snapshotGasCost(path.getGasCostOfDecodeFirstPool(encodePath([tokenAddresses[0], tokenAddresses[1], tokenAddresses[2]]))); }); }); diff --git a/src/periphery/test/PoolAddress.spec.ts b/src/periphery/test/PoolAddress.spec.ts index f5aab87ef..47ebc43e6 100644 --- a/src/periphery/test/PoolAddress.spec.ts +++ b/src/periphery/test/PoolAddress.spec.ts @@ -27,7 +27,7 @@ describe('PoolAddress', () => { describe('#computeAddress', () => { it('all arguments equal zero', async () => { - await expect(poolAddress.computeAddress(ZeroAddress, ZeroAddress, ZeroAddress, 0)).to.be.reverted; + await expect(poolAddress.computeAddress(ZeroAddress, ZeroAddress, ZeroAddress, ZeroAddress, 0)).to.be.reverted; }); it('matches example from core repo', async () => { @@ -36,6 +36,7 @@ describe('PoolAddress', () => { '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0x1000000000000000000000000000000000000000', '0x2000000000000000000000000000000000000000', + ZeroAddress, 250 ) ).to.matchSnapshot(); @@ -47,6 +48,7 @@ describe('PoolAddress', () => { '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0x2000000000000000000000000000000000000000', '0x1000000000000000000000000000000000000000', + ZeroAddress, 3000 ) ).to.be.reverted; @@ -58,6 +60,7 @@ describe('PoolAddress', () => { '0x5FbDB2315678afecb367f032d93F642f64180aa3', '0x1000000000000000000000000000000000000000', '0x2000000000000000000000000000000000000000', + ZeroAddress, 3000 ) ); diff --git a/src/periphery/test/PositionValue.spec.ts b/src/periphery/test/PositionValue.spec.ts index b6ba1dfaa..b7a01f6fe 100644 --- a/src/periphery/test/PositionValue.spec.ts +++ b/src/periphery/test/PositionValue.spec.ts @@ -18,6 +18,7 @@ import completeFixture from './shared/completeFixture'; import snapshotGasCost from './shared/snapshotGasCost'; import { expect } from './shared/expect'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; describe('PositionValue', async () => { let wallets: any; @@ -39,7 +40,7 @@ describe('PositionValue', async () => { await token.transfer(wallets[0].address, expandTo18Decimals(1_000_000)); } - await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokens[0], tokens[1], ZERO_ADDRESS, encodePriceSqrt(1, 1)); return { positionValue, @@ -80,6 +81,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: tokens[0], token1: tokens[1], + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -98,7 +100,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[0].getAddress(), await tokens[1].getAddress()]), + path: encodePath([await tokens[0].getAddress(), ZERO_ADDRESS, await tokens[1].getAddress()]), amountIn: swapAmount, amountOutMinimum: 0, }); @@ -107,7 +109,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[1].getAddress(), await tokens[0].getAddress()]), + path: encodePath([await tokens[1].getAddress(), ZERO_ADDRESS, await tokens[0].getAddress()]), amountIn: swapAmount, amountOutMinimum: 0, }); @@ -141,6 +143,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -160,6 +163,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: -60, recipient: wallets[0].address, @@ -179,6 +183,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: 60, tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -198,6 +203,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: -6_000, tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -217,6 +223,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: 6_000, recipient: wallets[0].address, @@ -236,6 +243,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -260,6 +268,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, @@ -276,6 +285,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: TICK_SPACINGS[FeeAmount.MEDIUM] * -1_000, tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM] * 1_000, recipient: wallets[0].address, @@ -294,7 +304,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[0].getAddress(), await tokens[1].getAddress()]), + path: encodePath([await tokens[0].getAddress(), ZERO_ADDRESS, await tokens[1].getAddress()]), amountIn: swapAmount, amountOutMinimum: 0, }); @@ -303,7 +313,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[1].getAddress(), await tokens[0].getAddress()]), + path: encodePath([await tokens[1].getAddress(), ZERO_ADDRESS, await tokens[0].getAddress()]), amountIn: swapAmount, amountOutMinimum: 0, }); @@ -339,7 +349,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[0].getAddress(), await tokens[1].getAddress()]), + path: encodePath([await tokens[0].getAddress(), ZERO_ADDRESS, await tokens[1].getAddress()]), amountIn: swapAmount, amountOutMinimum: 0, }); @@ -365,6 +375,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: TICK_SPACINGS[FeeAmount.MEDIUM] * -10, tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM] * 10, recipient: wallets[0].address, @@ -382,7 +393,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[1].getAddress(), await tokens[0].getAddress()]), + path: encodePath([await tokens[1].getAddress(), ZERO_ADDRESS, await tokens[0].getAddress()]), amountIn: expandTo18Decimals(1_000), amountOutMinimum: 0, }); @@ -391,7 +402,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[0].getAddress(), await tokens[1].getAddress()]), + path: encodePath([await tokens[0].getAddress(), ZERO_ADDRESS, await tokens[1].getAddress()]), amountIn: expandTo18Decimals(50_000), amountOutMinimum: 0, }); @@ -420,6 +431,7 @@ describe('PositionValue', async () => { await nft.mint({ token0: await tokens[0].getAddress(), token1: await tokens[1].getAddress(), + deployer: ZERO_ADDRESS, tickLower: TICK_SPACINGS[FeeAmount.MEDIUM] * -10, tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM] * 10, @@ -438,7 +450,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[0].getAddress(), await tokens[1].getAddress()]), + path: encodePath([await tokens[0].getAddress(), ZERO_ADDRESS, await tokens[1].getAddress()]), amountIn: expandTo18Decimals(1_000), amountOutMinimum: 0, }); @@ -447,7 +459,7 @@ describe('PositionValue', async () => { await router.exactInput({ recipient: wallets[0].address, deadline: 1, - path: encodePath([await tokens[1].getAddress(), await tokens[0].getAddress()]), + path: encodePath([await tokens[1].getAddress(), ZERO_ADDRESS, await tokens[0].getAddress()]), amountIn: expandTo18Decimals(50_000), amountOutMinimum: 0, }); diff --git a/src/periphery/test/Quoter.spec.ts b/src/periphery/test/Quoter.spec.ts index b2137d160..08ea4bf8b 100644 --- a/src/periphery/test/Quoter.spec.ts +++ b/src/periphery/test/Quoter.spec.ts @@ -2,11 +2,15 @@ import { Wallet, ContractTransactionResponse, MaxUint256, ZeroAddress } from 'et import { ethers } from 'hardhat'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { + AlgebraCustomPoolEntryPoint, + CustomPoolDeployerTest, IAlgebraFactory, IAlgebraPool, IWNativeToken, + MockPluginFactory, MockTimeNonfungiblePositionManager, MockTimeSwapRouter, + PoolAddressTest, Quoter, TestERC20, } from '../typechain'; @@ -17,6 +21,7 @@ import { expandTo18Decimals } from './shared/expandTo18Decimals'; import { expect } from './shared/expect'; import { encodePath } from './shared/path'; import { createPool } from './shared/quoter'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address: string }; @@ -27,13 +32,15 @@ describe('Quoter', () => { const swapRouterFixture: () => Promise<{ nft: MockTimeNonfungiblePositionManager; tokens: [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + customPoolDeployer: CustomPoolDeployerTest; + path: [string, string, string, string, string]; quoter: Quoter; router: MockTimeSwapRouter; wnative: IWNativeToken; factory: IAlgebraFactory; }> = async () => { let _tokens; - const { wnative, factory, router, tokens, nft } = await loadFixture(completeFixture); + const { wnative, factory, router, tokens, customPoolDeployer, path, nft } = await loadFixture(completeFixture); _tokens = tokens as [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; // approve & fund wallets @@ -50,6 +57,8 @@ describe('Quoter', () => { return { tokens: _tokens, + path: path, + customPoolDeployer: customPoolDeployer, nft, quoter, router, @@ -60,6 +69,7 @@ describe('Quoter', () => { let nft: MockTimeNonfungiblePositionManager; let tokens: [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + let path: [string, string, string, string, string]; let quoter: Quoter; let router: MockTimeSwapRouter; let wnative: IWNativeToken; @@ -72,20 +82,23 @@ describe('Quoter', () => { describe('quotes', () => { const subFixture = async () => { - const { tokens, nft, quoter, router, wnative, factory } = await swapRouterFixture(); - const pool0 = await createPool(nft, wallet, await tokens[0].getAddress(), await tokens[1].getAddress()); - await createPool(nft, wallet, await tokens[1].getAddress(), await tokens[2].getAddress()); - return { tokens, nft, quoter, router, wnative, factory }; + const { tokens, customPoolDeployer, path, nft, quoter, router, wnative, factory } = await swapRouterFixture(); + const pool0 = await createPool(nft, wallet, await tokens[0].getAddress(), await tokens[1].getAddress(), ZERO_ADDRESS); + + await customPoolDeployer.createCustomPool(customPoolDeployer, wallet.address, await tokens[1].getAddress(), await tokens[2].getAddress(), '0x'); + await createPool(nft, wallet, await tokens[1].getAddress(), await tokens[2].getAddress(), await customPoolDeployer.getAddress()); + + return { tokens, path, nft, quoter, router, wnative, factory }; }; beforeEach(async () => { - ({ tokens, nft, quoter, router, wnative, factory } = await loadFixture(subFixture)); + ({ tokens, path, nft, quoter, router, wnative, factory } = await loadFixture(subFixture)); }); describe('#quoteExactInput', () => { it('0 -> 1', async () => { const { amountOut, fees } = await quoter.quoteExactInput.staticCall( - encodePath([tokens[0].address, tokens[1].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[1].address]), 3 ); @@ -123,20 +136,20 @@ describe('Quoter', () => { } const { amountOut, fees } = await quoter.quoteExactInput.staticCall( - encodePath([tokens[0].address, tokens[1].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[1].address]), expandTo18Decimals(300000) ); expect(fees[0]).to.eq(500); - await exactInput([tokens[0].address, tokens[1].address], 300000); + await exactInput([tokens[0].address, ZERO_ADDRESS, tokens[1].address], 300000); await ethers.provider.send('evm_mine', []); await ethers.provider.send('evm_increaseTime', [60 * 60 * 3]); await ethers.provider.send('evm_mine', []); const { amountOut: amountOut2, fees: fees2 } = await quoter.quoteExactInput.staticCall( - encodePath([tokens[0].address, tokens[1].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[1].address]), expandTo18Decimals(300000) ); @@ -145,7 +158,7 @@ describe('Quoter', () => { it('1 -> 0', async () => { const { amountOut, fees } = await quoter.quoteExactInput.staticCall( - encodePath([tokens[1].address, tokens[0].address]), + encodePath([tokens[1].address, ZERO_ADDRESS, tokens[0].address]), 3 ); @@ -155,7 +168,7 @@ describe('Quoter', () => { it('0 -> 1 -> 2', async () => { const { amountOut, fees } = await quoter.quoteExactInput.staticCall( - encodePath(tokens.map((token) => token.address)), + encodePath(path), 5 ); @@ -165,7 +178,7 @@ describe('Quoter', () => { it('2 -> 1 -> 0', async () => { const { amountOut, fees } = await quoter.quoteExactInput.staticCall( - encodePath(tokens.map((token) => token.address).reverse()), + encodePath(path.slice().reverse()), 5 ); @@ -179,6 +192,7 @@ describe('Quoter', () => { const { amountOut, fee } = await quoter.quoteExactInputSingle.staticCall( tokens[0].address, tokens[1].address, + ZERO_ADDRESS, MaxUint128, // -2% encodePriceSqrt(100, 102) @@ -198,6 +212,7 @@ describe('Quoter', () => { quoter.quoteExactInputSingle.staticCall( tokens[1].address, tokens[0].address, + ZERO_ADDRESS, MaxUint128, // -2%, invalid direction encodePriceSqrt(98, 100) @@ -209,6 +224,7 @@ describe('Quoter', () => { const { amountOut, fee } = await quoter.quoteExactInputSingle.staticCall( tokens[1].address, tokens[0].address, + ZERO_ADDRESS, MaxUint128, // +2% encodePriceSqrt(102, 100) @@ -222,7 +238,7 @@ describe('Quoter', () => { describe('#quoteExactOutput', () => { it('0 -> 1', async () => { const { amountIn, fees } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[1].address, tokens[0].address]), + encodePath([tokens[1].address, ZERO_ADDRESS, tokens[0].address]), 1 ); @@ -232,7 +248,7 @@ describe('Quoter', () => { it('1 -> 0', async () => { const { amountIn, fees } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[0].address, tokens[1].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[1].address]), 1 ); @@ -242,7 +258,7 @@ describe('Quoter', () => { it('0 -> 1 -> 2', async () => { const { amountIn, fees } = await quoter.quoteExactOutput.staticCall( - encodePath(tokens.map((token) => token.address).reverse()), + encodePath(path), 1 ); @@ -252,7 +268,7 @@ describe('Quoter', () => { it('2 -> 1 -> 0', async () => { const { amountIn, fees } = await quoter.quoteExactOutput.staticCall( - encodePath(tokens.map((token) => token.address)), + encodePath(path), 1 ); @@ -266,6 +282,7 @@ describe('Quoter', () => { const { amountIn, fee } = await quoter.quoteExactOutputSingle.staticCall( tokens[0].address, tokens[1].address, + ZERO_ADDRESS, MaxUint128, encodePriceSqrt(100, 102) ); @@ -278,6 +295,7 @@ describe('Quoter', () => { const { amountIn, fee } = await quoter.quoteExactOutputSingle.staticCall( tokens[1].address, tokens[0].address, + ZERO_ADDRESS, MaxUint128, encodePriceSqrt(102, 100) ); diff --git a/src/periphery/test/QuoterV2.spec.ts b/src/periphery/test/QuoterV2.spec.ts index 82fccdee4..c478d6877 100644 --- a/src/periphery/test/QuoterV2.spec.ts +++ b/src/periphery/test/QuoterV2.spec.ts @@ -1,7 +1,7 @@ import { MaxUint256, Wallet } from 'ethers'; import { ethers } from 'hardhat'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { IAlgebraFactory, MockTimeNonfungiblePositionManager, QuoterV2, TestERC20 } from '../typechain'; +import { CustomPoolDeployerTest, IAlgebraFactory, MockTimeNonfungiblePositionManager, QuoterV2, TestERC20 } from '../typechain'; import completeFixture from './shared/completeFixture'; import { MaxUint128 } from './shared/constants'; import { encodePriceSqrt } from './shared/encodePriceSqrt'; @@ -10,6 +10,7 @@ import { expect } from './shared/expect'; import { encodePath } from './shared/path'; import { createPool, createPoolWithMultiplePositions, createPoolWithZeroTickInitialized } from './shared/quoter'; import snapshotGasCost from './shared/snapshotGasCost'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address: string }; @@ -21,10 +22,12 @@ describe('QuoterV2', function () { const swapRouterFixture: () => Promise<{ nft: MockTimeNonfungiblePositionManager; tokens: [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + customPoolDeployer: CustomPoolDeployerTest; + path: [string, string, string, string, string]; quoter: QuoterV2; factory: IAlgebraFactory; }> = async () => { - const { wnative, factory, router, tokens, nft } = await loadFixture(completeFixture); + const { wnative, factory, router, tokens, customPoolDeployer, path, nft } = await loadFixture(completeFixture); let _tokens = tokens as [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; // approve & fund wallets for (const token of _tokens) { @@ -40,6 +43,8 @@ describe('QuoterV2', function () { return { tokens: _tokens, + customPoolDeployer: customPoolDeployer, + path: path, nft, quoter, factory, @@ -48,6 +53,7 @@ describe('QuoterV2', function () { let nft: MockTimeNonfungiblePositionManager; let tokens: [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + let path: [string, string, string, string, string]; let quoter: QuoterV2; let factory: IAlgebraFactory; @@ -58,12 +64,15 @@ describe('QuoterV2', function () { describe('quotes', () => { const subFixture = async () => { - const { tokens, nft, quoter, factory } = await swapRouterFixture(); - await createPool(nft, wallet, tokens[0].address, tokens[1].address); - await createPool(nft, wallet, tokens[1].address, tokens[2].address); + const { tokens, customPoolDeployer, path, nft, quoter, factory } = await swapRouterFixture(); + await createPool(nft, wallet, tokens[0].address, tokens[1].address, ZERO_ADDRESS); + + await customPoolDeployer.createCustomPool(customPoolDeployer, wallet.address, await tokens[1].getAddress(), await tokens[2].getAddress(), '0x'); + await createPool(nft, wallet, tokens[1].address, tokens[2].address, await customPoolDeployer.getAddress()); await createPoolWithMultiplePositions(nft, wallet, tokens[0].address, tokens[2].address); return { tokens, + path, nft, quoter, factory, @@ -71,13 +80,13 @@ describe('QuoterV2', function () { }; beforeEach(async () => { - ({ tokens, nft, quoter, factory } = await loadFixture(subFixture)); + ({ tokens, path, nft, quoter, factory } = await loadFixture(subFixture)); }); describe('#quoteExactInput', () => { it('0 -> 2 cross 2 tick', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 10000); + await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 10000); ////await snapshotGasCost(gasEstimate) expect(sqrtPriceX96AfterList.length).to.eq(1); @@ -91,7 +100,7 @@ describe('QuoterV2', function () { // 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 } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 6200); + await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 6200); ////await snapshotGasCost(gasEstimate) expect(sqrtPriceX96AfterList.length).to.eq(1); @@ -104,7 +113,7 @@ describe('QuoterV2', function () { it('0 -> 2 cross 1 tick', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 4000); + await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 4000); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(1); @@ -117,7 +126,7 @@ describe('QuoterV2', function () { it('0 -> 2 cross 0 tick, starting tick not initialized', async () => { // Tick before 0, tick after -1. const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 10); + await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 10); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(0); @@ -132,7 +141,7 @@ describe('QuoterV2', function () { await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 10); + await quoter.quoteExactInput.staticCall(encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 10); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(1); @@ -144,7 +153,7 @@ describe('QuoterV2', function () { it('2 -> 0 cross 2', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 10000); + await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 10000); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(2); @@ -160,7 +169,7 @@ describe('QuoterV2', function () { // 120 is an initialized tick for this pool. We check we don't count it. const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 6250); + await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 6250); ////await snapshotGasCost(gasEstimate) console.log(sqrtPriceX96AfterList[0].toString()); @@ -177,7 +186,7 @@ describe('QuoterV2', function () { await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 200); + await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 200); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(0); @@ -191,7 +200,7 @@ describe('QuoterV2', function () { it('2 -> 0 cross 0 tick, starting tick not initialized', async () => { // Tick 0 initialized. Tick after = 1 const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 103); + await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 103); ////await snapshotGasCost(gasEstimate) expect(initializedTicksCrossedList[0]).to.eq(0); @@ -204,7 +213,8 @@ describe('QuoterV2', function () { it('2 -> 1', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactInput.staticCall(encodePath([tokens[2].address, tokens[1].address]), 10000); + + await quoter.quoteExactInput.staticCall(encodePath([path[4], path[3], path[2]]), 10000); ////await snapshotGasCost(gasEstimate) expect(sqrtPriceX96AfterList.length).to.eq(1); @@ -217,7 +227,7 @@ describe('QuoterV2', function () { it('0 -> 2 -> 1', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactInput.staticCall( - encodePath([tokens[0].address, tokens[2].address, tokens[1].address]), + encodePath([path[0], ZERO_ADDRESS, path[4], path[3], path[2]]), 10000 ); @@ -243,6 +253,7 @@ describe('QuoterV2', function () { } = await quoter.quoteExactInputSingle.staticCall({ tokenIn: tokens[0].address, tokenOut: tokens[2].address, + deployer: ZERO_ADDRESS, amountIn: MaxUint128, // -2% limitSqrtPrice: encodePriceSqrt(100, 102), @@ -265,6 +276,7 @@ describe('QuoterV2', function () { quoter.quoteExactInputSingle.staticCall({ tokenIn: tokens[0].address, tokenOut: tokens[2].address, + deployer: ZERO_ADDRESS, amountIn: MaxUint128, // +2% limitSqrtPrice: encodePriceSqrt(104, 102), @@ -282,6 +294,8 @@ describe('QuoterV2', function () { } = await quoter.quoteExactInputSingle.staticCall({ tokenIn: tokens[2].address, tokenOut: tokens[0].address, + deployer: ZERO_ADDRESS, + amountIn: MaxUint128, // +2% limitSqrtPrice: encodePriceSqrt(102, 100), @@ -299,6 +313,7 @@ describe('QuoterV2', function () { const { gasEstimate } = await quoter.quoteExactInputSingle.staticCall({ tokenIn: tokens[0].address, tokenOut: tokens[2].address, + deployer: ZERO_ADDRESS, amountIn: 10000, // -2% limitSqrtPrice: encodePriceSqrt(100, 102), @@ -311,6 +326,7 @@ describe('QuoterV2', function () { const { gasEstimate } = await quoter.quoteExactInputSingle.staticCall({ tokenIn: tokens[2].address, tokenOut: tokens[0].address, + deployer: ZERO_ADDRESS, amountIn: 10000, // +2% limitSqrtPrice: encodePriceSqrt(102, 100), @@ -324,7 +340,7 @@ describe('QuoterV2', function () { describe('#quoteExactOutput', () => { it('0 -> 2 cross 2 tick', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 15000); + 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); @@ -339,7 +355,7 @@ describe('QuoterV2', function () { // 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 } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 6158); + 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'); @@ -351,7 +367,7 @@ describe('QuoterV2', function () { it('0 -> 2 cross 1 tick', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 4000); + 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); @@ -366,7 +382,7 @@ describe('QuoterV2', function () { // Tick before 0, tick after 1. Tick 0 initialized. await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 100); + 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); @@ -379,7 +395,7 @@ describe('QuoterV2', function () { it('0 -> 2 cross 0 tick starting tick not initialized', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[2].address, tokens[0].address]), 10); + 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); @@ -392,7 +408,7 @@ describe('QuoterV2', function () { it('2 -> 0 cross 2 ticks', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 15000); + 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); @@ -406,7 +422,7 @@ describe('QuoterV2', function () { // 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 } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 6223); + 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); @@ -418,7 +434,7 @@ describe('QuoterV2', function () { it('2 -> 0 cross 1 tick', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[0].address, tokens[2].address]), 6000); + 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); @@ -430,7 +446,7 @@ describe('QuoterV2', function () { it('2 -> 1', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = - await quoter.quoteExactOutput.staticCall(encodePath([tokens[1].address, tokens[2].address]), 9897); + await quoter.quoteExactOutput.staticCall(encodePath([path[2], path[3], path[4]]), 9897); expect(sqrtPriceX96AfterList.length).to.eq(1); expect(sqrtPriceX96AfterList[0]).to.eq('80020121658316697953186638498'); @@ -442,7 +458,7 @@ describe('QuoterV2', function () { it('0 -> 2 -> 1', async () => { const { amountOut, amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[0].address, tokens[2].address, tokens[1].address].reverse()), + encodePath([path[0], ZERO_ADDRESS, path[4], path[3], path[2]].reverse()), 9795 ); @@ -458,7 +474,7 @@ describe('QuoterV2', function () { describe('gas [ @skip-on-coverage ]', () => { it('0 -> 2 cross 2 tick', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[2].address, tokens[0].address]), + encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 15000 ); @@ -469,7 +485,7 @@ describe('QuoterV2', function () { // 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 { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[2].address, tokens[0].address]), + encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 6158 ); @@ -478,7 +494,7 @@ describe('QuoterV2', function () { it('0 -> 2 cross 1 tick', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[2].address, tokens[0].address]), + encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 4000 ); @@ -489,7 +505,7 @@ describe('QuoterV2', function () { // Tick before 0, tick after 1. Tick 0 initialized. await createPoolWithZeroTickInitialized(nft, wallet, tokens[0].address, tokens[2].address); const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[2].address, tokens[0].address]), + encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 100 ); @@ -498,7 +514,7 @@ describe('QuoterV2', function () { it('0 -> 2 cross 0 tick starting tick not initialized', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[2].address, tokens[0].address]), + encodePath([tokens[2].address, ZERO_ADDRESS, tokens[0].address]), 10 ); @@ -507,7 +523,7 @@ describe('QuoterV2', function () { it('2 -> 0 cross 2 ticks', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[0].address, tokens[2].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 15000 ); @@ -518,7 +534,7 @@ describe('QuoterV2', function () { // 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 { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[0].address, tokens[2].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 6223 ); @@ -527,7 +543,7 @@ describe('QuoterV2', function () { it('2 -> 0 cross 1 tick', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[0].address, tokens[2].address]), + encodePath([tokens[0].address, ZERO_ADDRESS, tokens[2].address]), 6000 ); @@ -536,7 +552,7 @@ describe('QuoterV2', function () { it('2 -> 1', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[1].address, tokens[2].address]), + encodePath([path[2], path[3], path[4]]), 9897 ); @@ -545,7 +561,7 @@ describe('QuoterV2', function () { it('0 -> 2 -> 1', async () => { const { gasEstimate } = await quoter.quoteExactOutput.staticCall( - encodePath([tokens[0].address, tokens[2].address, tokens[1].address].reverse()), + encodePath([path[0], ZERO_ADDRESS, path[4], path[3], path[2]].reverse()), 9795 ); @@ -560,6 +576,7 @@ describe('QuoterV2', function () { await quoter.quoteExactOutputSingle.staticCall({ tokenIn: tokens[0].address, tokenOut: tokens[1].address, + deployer: ZERO_ADDRESS, amount: MaxUint128, limitSqrtPrice: encodePriceSqrt(100, 102), }); @@ -575,6 +592,7 @@ describe('QuoterV2', function () { await quoter.quoteExactOutputSingle.staticCall({ tokenIn: tokens[1].address, tokenOut: tokens[0].address, + deployer: ZERO_ADDRESS, amount: MaxUint128, limitSqrtPrice: encodePriceSqrt(102, 100), }); @@ -590,6 +608,7 @@ describe('QuoterV2', function () { const { gasEstimate } = await quoter.quoteExactOutputSingle.staticCall({ tokenIn: tokens[0].address, tokenOut: tokens[1].address, + deployer: ZERO_ADDRESS, amount: MaxUint128, limitSqrtPrice: encodePriceSqrt(100, 102), }); @@ -601,6 +620,7 @@ describe('QuoterV2', function () { const { gasEstimate } = await quoter.quoteExactOutputSingle.staticCall({ tokenIn: tokens[1].address, tokenOut: tokens[0].address, + deployer: ZERO_ADDRESS, amount: MaxUint128, limitSqrtPrice: encodePriceSqrt(102, 100), }); diff --git a/src/periphery/test/SwapRouter.spec.ts b/src/periphery/test/SwapRouter.spec.ts index 388f6de63..2e358112e 100644 --- a/src/periphery/test/SwapRouter.spec.ts +++ b/src/periphery/test/SwapRouter.spec.ts @@ -9,7 +9,8 @@ import { expandTo18Decimals } from './shared/expandTo18Decimals'; import { expect } from './shared/expect'; import { encodePath } from './shared/path'; import { getMaxTick, getMinTick } from './shared/ticks'; -import { computePoolAddress } from './shared/computePoolAddress'; +import { computePoolAddress, computeCustomPoolAddress } from './shared/computePoolAddress'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address: string }; @@ -23,6 +24,8 @@ describe('SwapRouter', function () { let router: MockTimeSwapRouter; let nft: MockTimeNonfungiblePositionManager; let tokens: [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + let path: [string, string, string, string, string]; + let getBalances: (who: string | MockTimeSwapRouter) => Promise<{ wnative: bigint; token0: bigint; @@ -36,16 +39,18 @@ describe('SwapRouter', function () { _nft: MockTimeNonfungiblePositionManager, _wallet: Wallet, tokenAddressA: string, - tokenAddressB: string + tokenAddressB: string, + deployer: string ) { if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, encodePriceSqrt(1, 1)); + await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, deployer, encodePriceSqrt(1, 1)); const liquidityParams = { token0: tokenAddressA, token1: tokenAddressB, + deployer: deployer, fee: FeeAmount.MEDIUM, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), @@ -66,9 +71,11 @@ describe('SwapRouter', function () { router: MockTimeSwapRouter; nft: MockTimeNonfungiblePositionManager; tokens: [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + path: [string, string, string, string, string]; }> = async () => { - const { wnative, factory, router, tokens, nft } = await loadFixture(completeFixture); + const { wnative, factory, router, tokens, customPoolDeployer, path, nft } = await loadFixture(completeFixture); let _tokens = tokens as [TestERC20WithAddress, TestERC20WithAddress, TestERC20WithAddress]; + // approve & fund wallets for (const token of _tokens) { await token.approve(router, MaxUint256); @@ -78,14 +85,17 @@ describe('SwapRouter', function () { token.address = await token.getAddress(); } - await createPool(nft, wallet, _tokens[0].address, _tokens[1].address); - await createPool(nft, wallet, _tokens[1].address, _tokens[2].address); + await createPool(nft, wallet, _tokens[0].address, _tokens[1].address, ZERO_ADDRESS); + await customPoolDeployer.createCustomPool(customPoolDeployer, wallet.address, _tokens[1].getAddress(), _tokens[2].getAddress(), '0x'); + await createPool(nft, wallet, _tokens[1].address, _tokens[2].address, await customPoolDeployer.getAddress()); + return { wnative, factory: factory as any as Contract, router, tokens: _tokens, + path: path, nft, }; }; @@ -93,7 +103,7 @@ describe('SwapRouter', function () { async function createPoolWNativeToken(tokenAddress: string) { await wnative.deposit({ value: liquidity }); await wnative.approve(nft, MaxUint256); - return createPool(nft, wallet, await wnative.getAddress(), tokenAddress); + return createPool(nft, wallet, await wnative.getAddress(), tokenAddress, ZERO_ADDRESS); } before('create fixture loader', async () => { @@ -102,7 +112,7 @@ describe('SwapRouter', function () { // helper for getting wnative and token balances beforeEach('load fixture', async () => { - ({ router, wnative, factory, tokens, nft } = await loadFixture(swapRouterFixture)); + ({ router, wnative, factory, tokens, path, nft } = await loadFixture(swapRouterFixture)); getBalances = async (who: string | MockTimeSwapRouter) => { let addr; @@ -153,6 +163,7 @@ describe('SwapRouter', function () { router.exactInputSingle({ tokenIn: tokens[0].address, tokenOut: tokens[1].address, + deployer: ZERO_ADDRESS, limitSqrtPrice: 0, amountOutMinimum: 0, deadline: 1, @@ -201,7 +212,7 @@ describe('SwapRouter', function () { it('reverts if deadline passed', async () => { await expect( exactInput( - tokens.slice(0, 2).map((token) => token.address), + path.slice(0, 3), 3, 1, 2 @@ -217,7 +228,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await exactInput(tokens.slice(0, 2).map((token) => token.address)); + await exactInput(path.slice(0, 3)); // get balances after const poolAfter = await getBalances(pool); @@ -237,10 +248,9 @@ describe('SwapRouter', function () { const traderBefore = await getBalances(trader.address); await exactInput( - tokens - .slice(0, 2) + path + .slice(0, 3) .reverse() - .map((token) => token.address) ); // get balances after @@ -259,7 +269,7 @@ describe('SwapRouter', function () { const traderBefore = await getBalances(trader.address); await exactInput( - tokens.map((token) => token.address), + path, 5, 1 ); @@ -273,7 +283,7 @@ describe('SwapRouter', function () { it('2 -> 1 -> 0', async () => { const traderBefore = await getBalances(trader.address); - await exactInput(tokens.map((token) => token.address).reverse(), 5, 1); + await exactInput(path.slice().reverse(), 5, 1); const traderAfter = await getBalances(trader.address); @@ -284,7 +294,7 @@ describe('SwapRouter', function () { it('events', async () => { await expect( exactInput( - tokens.map((token) => token.address), + path, 5, 1 ) @@ -304,12 +314,12 @@ describe('SwapRouter', function () { .to.emit(tokens[1], 'Transfer') .withArgs( await router.getAddress(), - computePoolAddress(await factory.poolDeployer(), [tokens[1].address, tokens[2].address]), + computeCustomPoolAddress(await factory.poolDeployer(), [path[2], path[3], path[4]]), 3 ) .to.emit(tokens[2], 'Transfer') .withArgs( - computePoolAddress(await factory.poolDeployer(), [tokens[1].address, tokens[2].address]), + computeCustomPoolAddress(await factory.poolDeployer(), [path[2], path[3], path[4]]), trader.address, 1 ); @@ -329,7 +339,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactInput([await wnative.getAddress(), tokens[0].address])) + await expect(exactInput([await wnative.getAddress(), ZERO_ADDRESS, tokens[0].address])) .to.emit(wnative, 'Deposit') .withArgs(await router.getAddress(), 3); @@ -345,7 +355,7 @@ describe('SwapRouter', function () { it('WNativeToken -> 0 -> 1', async () => { const traderBefore = await getBalances(trader.address); - await expect(exactInput([await wnative.getAddress(), tokens[0].address, tokens[1].address], 5)) + await expect(exactInput([await wnative.getAddress(), ZERO_ADDRESS, tokens[0].address, ZERO_ADDRESS, tokens[1].address], 5)) .to.emit(wnative, 'Deposit') .withArgs(await router.getAddress(), 5); @@ -370,7 +380,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactInput([tokens[0].address, await wnative.getAddress()])) + await expect(exactInput([tokens[0].address, ZERO_ADDRESS, await wnative.getAddress()])) .to.emit(wnative, 'Withdrawal') .withArgs(await router.getAddress(), 1); @@ -387,7 +397,7 @@ describe('SwapRouter', function () { // get balances before const traderBefore = await getBalances(trader.address); - await expect(exactInput([tokens[0].address, tokens[1].address, await wnative.getAddress()], 5)) + await expect(exactInput([tokens[0].address, ZERO_ADDRESS, tokens[1].address, ZERO_ADDRESS, await wnative.getAddress()], 5)) .to.emit(wnative, 'Withdrawal') .withArgs(await router.getAddress(), 1); @@ -404,6 +414,7 @@ describe('SwapRouter', function () { async function exactInputSingle( tokenIn: string, tokenOut: string, + deployer: string, amountIn: number = 3, amountOutMinimum: number = 1, limitSqrtPrice?: bigint, @@ -417,6 +428,7 @@ describe('SwapRouter', function () { const params = { tokenIn, tokenOut, + deployer: deployer, fee: FeeAmount.MEDIUM, limitSqrtPrice: limitSqrtPrice ?? tokenIn.toLowerCase() < tokenOut.toLowerCase() @@ -434,6 +446,7 @@ describe('SwapRouter', function () { // ensure that the swap fails if the limit is any tighter params.amountOutMinimum += 1; + // await router.connect(trader).exactInputSingle(params, { value }) await expect(router.connect(trader).exactInputSingle(params, { value })).to.be.revertedWith( 'Too little received' ); @@ -447,7 +460,7 @@ describe('SwapRouter', function () { } it('reverts if deadline passed', async () => { - await expect(exactInputSingle(tokens[0].address, tokens[1].address, 3, 1, undefined, 2)).to.be.revertedWith( + await expect(exactInputSingle(tokens[0].address, tokens[1].address, ZERO_ADDRESS, 3, 1, undefined, 2)).to.be.revertedWith( 'Transaction too old' ); }); @@ -459,7 +472,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await exactInputSingle(tokens[0].address, tokens[1].address); + await exactInputSingle(tokens[0].address, tokens[1].address, ZERO_ADDRESS); // get balances after const poolAfter = await getBalances(pool); @@ -478,7 +491,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await exactInputSingle(tokens[1].address, tokens[0].address); + await exactInputSingle(tokens[1].address, tokens[0].address, ZERO_ADDRESS); // get balances after const poolAfter = await getBalances(pool); @@ -503,7 +516,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactInputSingle(await wnative.getAddress(), tokens[0].address)) + await expect(exactInputSingle(await wnative.getAddress(), tokens[0].address, ZERO_ADDRESS)) .to.emit(wnative, 'Deposit') .withArgs(await router.getAddress(), 3); @@ -532,7 +545,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactInputSingle(tokens[0].address, await wnative.getAddress())) + await expect(exactInputSingle(tokens[0].address, await wnative.getAddress(), ZERO_ADDRESS)) .to.emit(wnative, 'Withdrawal') .withArgs(await router.getAddress(), 1); @@ -552,6 +565,7 @@ describe('SwapRouter', function () { async function exactInputSingleSupportingFeeOnTransferTokens( tokenIn: string, tokenOut: string, + deployer: string, amountIn: number = 300000, amountOutMinimum: number = 100000, limitSqrtPrice?: bigint, @@ -565,6 +579,7 @@ describe('SwapRouter', function () { const params = { tokenIn, tokenOut, + deployer: deployer, limitSqrtPrice: limitSqrtPrice ?? tokenIn.toLowerCase() < tokenOut.toLowerCase() ? BigInt('4295128740') @@ -581,6 +596,7 @@ describe('SwapRouter', function () { // ensure that the swap fails if the limit is tighter params.amountOutMinimum *= 5; + // await router.connect(trader).exactInputSingleSupportingFeeOnTransferTokens(params) await expect(router.connect(trader).exactInputSingleSupportingFeeOnTransferTokens(params)).to.be.revertedWith( 'Too little received' ); @@ -600,8 +616,9 @@ describe('SwapRouter', function () { }); it('reverts if deadline passed', async () => { + // await exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, tokens[1].address, ZERO_ADDRESS, 3, 1, undefined, 2) await expect( - exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, tokens[1].address, 3, 1, undefined, 2) + exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, tokens[1].address, ZERO_ADDRESS, 3, 1, undefined, 2) ).to.be.revertedWith('Transaction too old'); }); @@ -612,7 +629,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, tokens[1].address); + await exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, tokens[1].address, ZERO_ADDRESS); // get balances after const poolAfter = await getBalances(pool); @@ -631,7 +648,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await exactInputSingleSupportingFeeOnTransferTokens(tokens[1].address, tokens[0].address); + await exactInputSingleSupportingFeeOnTransferTokens(tokens[1].address, tokens[0].address, ZERO_ADDRESS); // get balances after const poolAfter = await getBalances(pool); @@ -657,7 +674,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, await wnative.getAddress())) + await expect(exactInputSingleSupportingFeeOnTransferTokens(tokens[0].address, await wnative.getAddress(), ZERO_ADDRESS)) .to.emit(wnative, 'Withdrawal') .withArgs(await router.getAddress(), 219146); @@ -701,6 +718,7 @@ describe('SwapRouter', function () { // ensure that the swap fails if the limit is any tighter params.amountInMaximum -= 1; + // router.connect(trader).exactOutput(params, { value }) await expect(router.connect(trader).exactOutput(params, { value })).to.be.revertedWith('Too much requested'); params.amountInMaximum += 1; @@ -711,7 +729,7 @@ describe('SwapRouter', function () { it('reverts if deadline passed', async () => { await expect( exactOutput( - tokens.slice(0, 2).map((token) => token.address), + path.slice(0, 3), 1, 3, 2 @@ -726,8 +744,8 @@ describe('SwapRouter', function () { // get balances before const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - - await exactOutput(tokens.slice(0, 2).map((token) => token.address)); + + await exactOutput(path.slice(0, 3)); // get balances after const poolAfter = await getBalances(pool); @@ -747,10 +765,9 @@ describe('SwapRouter', function () { const traderBefore = await getBalances(trader.address); await exactOutput( - tokens - .slice(0, 2) + path + .slice(0, 3) .reverse() - .map((token) => token.address) ); // get balances after @@ -769,7 +786,7 @@ describe('SwapRouter', function () { const traderBefore = await getBalances(trader.address); await exactOutput( - tokens.map((token) => token.address), + path, 1, 5 ); @@ -783,7 +800,7 @@ describe('SwapRouter', function () { it('2 -> 1 -> 0', async () => { const traderBefore = await getBalances(trader.address); - await exactOutput(tokens.map((token) => token.address).reverse(), 1, 5); + await exactOutput(path.slice().reverse(), 1, 5); const traderAfter = await getBalances(trader.address); @@ -794,21 +811,21 @@ describe('SwapRouter', function () { it('events', async () => { await expect( exactOutput( - tokens.map((token) => token.address), + path, 1, 5 ) ) .to.emit(tokens[2], 'Transfer') .withArgs( - computePoolAddress(await factory.poolDeployer(), [tokens[2].address, tokens[1].address]), + computeCustomPoolAddress(await factory.poolDeployer(), [path[4], path[3], path[2]]), trader.address, 1 ) .to.emit(tokens[1], 'Transfer') .withArgs( computePoolAddress(await factory.poolDeployer(), [tokens[1].address, tokens[0].address]), - computePoolAddress(await factory.poolDeployer(), [tokens[2].address, tokens[1].address]), + computeCustomPoolAddress(await factory.poolDeployer(), [path[4], path[3], path[2]]), 3 ) .to.emit(tokens[0], 'Transfer') @@ -833,7 +850,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactOutput([await wnative.getAddress(), tokens[0].address])) + await expect(exactOutput([await wnative.getAddress(), ZERO_ADDRESS, tokens[0].address])) .to.emit(wnative, 'Deposit') .withArgs(await router.getAddress(), 3); @@ -849,7 +866,7 @@ describe('SwapRouter', function () { it('WNativeToken -> 0 -> 1', async () => { const traderBefore = await getBalances(trader.address); - await expect(exactOutput([await wnative.getAddress(), tokens[0].address, tokens[1].address], 1, 5)) + await expect(exactOutput([await wnative.getAddress(), ZERO_ADDRESS, tokens[0].address, ZERO_ADDRESS, tokens[1].address], 1, 5)) .to.emit(wnative, 'Deposit') .withArgs(await router.getAddress(), 5); @@ -874,7 +891,7 @@ describe('SwapRouter', function () { const poolBefore = await getBalances(pool); const traderBefore = await getBalances(trader.address); - await expect(exactOutput([tokens[0].address, await wnative.getAddress()])) + await expect(exactOutput([tokens[0].address, ZERO_ADDRESS, await wnative.getAddress()])) .to.emit(wnative, 'Withdrawal') .withArgs(await router.getAddress(), 1); @@ -891,7 +908,7 @@ describe('SwapRouter', function () { // get balances before const traderBefore = await getBalances(trader.address); - await expect(exactOutput([tokens[0].address, tokens[1].address, await wnative.getAddress()], 1, 5)) + await expect(exactOutput([tokens[0].address, ZERO_ADDRESS, tokens[1].address, ZERO_ADDRESS, await wnative.getAddress()], 1, 5)) .to.emit(wnative, 'Withdrawal') .withArgs(await router.getAddress(), 1); @@ -921,6 +938,7 @@ describe('SwapRouter', function () { const params = { tokenIn, tokenOut, + deployer: ZERO_ADDRESS, fee: FeeAmount.MEDIUM, recipient: outputIsWNativeToken ? ZeroAddress : trader.address, deadline: 1, @@ -1057,7 +1075,7 @@ describe('SwapRouter', function () { it('#sweepTokenWithFee', async () => { const amountOutMinimum = 100; const params = { - path: encodePath([tokens[0].address, tokens[1].address]), + path: encodePath([tokens[0].address, ZERO_ADDRESS, tokens[1].address]), recipient: await router.getAddress(), deadline: 1, amountIn: 102, @@ -1087,7 +1105,7 @@ describe('SwapRouter', function () { const amountOutMinimum = 100; const params = { - path: encodePath([tokens[0].address, await wnative.getAddress()]), + path: encodePath([tokens[0].address, ZERO_ADDRESS, await wnative.getAddress()]), recipient: await router.getAddress(), deadline: 1, amountIn: 102, diff --git a/src/periphery/test/TickLens.spec.ts b/src/periphery/test/TickLens.spec.ts index 97011eef8..d9a62ca9a 100644 --- a/src/periphery/test/TickLens.spec.ts +++ b/src/periphery/test/TickLens.spec.ts @@ -9,6 +9,7 @@ import { expect } from './shared/expect'; import { getMaxTick, getMinTick } from './shared/ticks'; import { computePoolAddress } from './shared/computePoolAddress'; import snapshotGasCost from './shared/snapshotGasCost'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address: string }; @@ -53,6 +54,7 @@ describe('TickLens', () => { const mintParams = { token0: tokens[0].address, token1: tokens[1].address, + deployer: ZERO_ADDRESS, tickLower, tickUpper, amount0Desired: amountBothDesired, @@ -76,12 +78,13 @@ describe('TickLens', () => { if (BigInt(tokenAddressA) > BigInt(tokenAddressB)) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - const tx = await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, encodePriceSqrt(1, 1)); + const tx = await _nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await tx.wait(); const liquidityParams = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallets[0].address, diff --git a/src/periphery/test/V3Migrator.spec.ts b/src/periphery/test/V3Migrator.spec.ts index e3457ea67..ea60a44bf 100644 --- a/src/periphery/test/V3Migrator.spec.ts +++ b/src/periphery/test/V3Migrator.spec.ts @@ -18,6 +18,7 @@ import { encodePriceSqrt } from './shared/encodePriceSqrt'; import snapshotGasCost from './shared/snapshotGasCost'; import { sortedTokens } from './shared/tokenSort'; import { getMaxTick, getMinTick } from './shared/ticks'; +import { ZERO_ADDRESS } from './CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address: string | undefined }; @@ -128,6 +129,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? await token.getAddress() : await wnative.getAddress(), token1: tokenLower ? await wnative.getAddress() : await token.getAddress(), + deployer: ZERO_ADDRESS, tickLower: -1, tickUpper: 1, amount0Min: 9000, @@ -141,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, encodePriceSqrt(1, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await pair.approve(migrator, expectedLiquidity); await migrator.migrate({ @@ -150,6 +152,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? await token.getAddress() : await wnative.getAddress(), token1: tokenLower ? await wnative.getAddress() : await token.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 9000, @@ -169,7 +172,7 @@ describe('V3Migrator', () => { it('works for partial', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(1, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); const tokenBalanceBefore = await token.balanceOf(wallet.address); const wnativeBalanceBefore = await wnative.balanceOf(wallet.address); @@ -181,6 +184,7 @@ describe('V3Migrator', () => { percentageToMigrate: 50, token0: tokenLower ? await token.getAddress() : await wnative.getAddress(), token1: tokenLower ? await wnative.getAddress() : await token.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 4500, @@ -206,7 +210,7 @@ describe('V3Migrator', () => { it('double the price', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(2, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(2, 1)); const tokenBalanceBefore = await token.balanceOf(wallet.address); const wnativeBalanceBefore = await wnative.balanceOf(wallet.address); @@ -218,6 +222,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? await token.getAddress() : await wnative.getAddress(), token1: tokenLower ? await wnative.getAddress() : await token.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 4500, @@ -249,7 +254,7 @@ describe('V3Migrator', () => { it('half the price', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(1, 2)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 2)); const tokenBalanceBefore = await token.balanceOf(wallet.address); const wnativeBalanceBefore = await wnative.balanceOf(wallet.address); @@ -261,6 +266,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? token : wnative, token1: tokenLower ? wnative : token, + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 8999, @@ -292,7 +298,7 @@ describe('V3Migrator', () => { it('double the price - as Native', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(2, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(2, 1)); const tokenBalanceBefore = await token.balanceOf(wallet.address); @@ -304,6 +310,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? token : wnative, token1: tokenLower ? wnative : token, + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 4500, @@ -335,7 +342,7 @@ describe('V3Migrator', () => { it('half the price - as Native', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(1, 2)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 2)); const tokenBalanceBefore = await token.balanceOf(wallet.address); @@ -347,6 +354,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? token : wnative, token1: tokenLower ? wnative : token, + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 8999, @@ -378,7 +386,7 @@ describe('V3Migrator', () => { it('gas [ @skip-on-coverage ]', async () => { const [token0, token1] = await sortedTokens(wnative, token); - await migrator.createAndInitializePoolIfNecessary(token0, token1, encodePriceSqrt(1, 1)); + await migrator.createAndInitializePoolIfNecessary(token0, token1, ZERO_ADDRESS, encodePriceSqrt(1, 1)); await pair.approve(migrator, expectedLiquidity); await snapshotGasCost( @@ -388,6 +396,7 @@ describe('V3Migrator', () => { percentageToMigrate: 100, token0: tokenLower ? await token.getAddress() : await wnative.getAddress(), token1: tokenLower ? await wnative.getAddress() : await token.getAddress(), + deployer: ZERO_ADDRESS, tickLower: getMinTick(60), tickUpper: getMaxTick(60), amount0Min: 9000, diff --git a/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap b/src/periphery/test/__snapshots__/NFTDescriptor.spec.ts.snap index 84b6fb99c..f245f1c65 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`] = `1616588`; +exports[`NFTDescriptor #constructTokenURI gas [ @skip-on-coverage ] 1`] = `1625388`; 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 e3a2aaa68..8f500b7c3 100644 --- a/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap +++ b/src/periphery/test/__snapshots__/NonfungiblePositionManager.spec.ts.snap @@ -1,39 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `61849`; +exports[`NonfungiblePositionManager #burn gas [ @skip-on-coverage ] 1`] = `62302`; -exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `120619`; +exports[`NonfungiblePositionManager #collect gas transfers both [ @skip-on-coverage ] 1`] = `123658`; -exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `116957`; +exports[`NonfungiblePositionManager #collect gas transfers token0 only [ @skip-on-coverage ] 1`] = `120002`; -exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `117148`; +exports[`NonfungiblePositionManager #collect gas transfers token1 only [ @skip-on-coverage ] 1`] = `120193`; -exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `4813021`; +exports[`NonfungiblePositionManager #createAndInitializePoolIfNecessary gas [ @skip-on-coverage ] 1`] = `5263839`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `167685`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas complete decrease [ @skip-on-coverage ] 1`] = `169445`; -exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `171771`; +exports[`NonfungiblePositionManager #decreaseLiquidity gas partial decrease [ @skip-on-coverage ] 1`] = `174001`; -exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `177588`; +exports[`NonfungiblePositionManager #increaseLiquidity gas [ @skip-on-coverage ] 1`] = `180336`; -exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `628151`; +exports[`NonfungiblePositionManager #mint gas first mint for pool [ @skip-on-coverage ] 1`] = `632773`; -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 non-zero refund [ @skip-on-coverage ] 1`] = `647136`; -exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `634357`; +exports[`NonfungiblePositionManager #mint gas first mint for pool using eth with zero refund [ @skip-on-coverage ] 1`] = `639969`; -exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `436164`; +exports[`NonfungiblePositionManager #mint gas mint for same pool, different ticks [ @skip-on-coverage ] 1`] = `438473`; -exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `325974`; +exports[`NonfungiblePositionManager #mint gas mint on same ticks [ @skip-on-coverage ] 1`] = `328331`; -exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `59310`; +exports[`NonfungiblePositionManager #permit owned by eoa gas [ @skip-on-coverage ] 1`] = `60014`; -exports[`NonfungiblePositionManager #permit owned by verifying contract gas [ @skip-on-coverage ] 1`] = `63128`; +exports[`NonfungiblePositionManager #permit owned by verifying contract gas [ @skip-on-coverage ] 1`] = `63880`; -exports[`NonfungiblePositionManager #transferFrom gas [ @skip-on-coverage ] 1`] = `86049`; +exports[`NonfungiblePositionManager #transferFrom gas [ @skip-on-coverage ] 1`] = `86323`; -exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @skip-on-coverage ] 1`] = `86832`; +exports[`NonfungiblePositionManager #transferFrom gas comes from approved [ @skip-on-coverage ] 1`] = `87247`; -exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `24267`; +exports[`NonfungiblePositionManager bytecode size [ @skip-on-coverage ] 1`] = `21884`; -exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `241370`; +exports[`NonfungiblePositionManager multicall exit gas [ @skip-on-coverage ] 1`] = `245031`; diff --git a/src/periphery/test/__snapshots__/Path.spec.ts.snap b/src/periphery/test/__snapshots__/Path.spec.ts.snap index 12dec0f48..c1b58f53a 100644 --- a/src/periphery/test/__snapshots__/Path.spec.ts.snap +++ b/src/periphery/test/__snapshots__/Path.spec.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Path gas cost [ @skip-on-coverage ] 1`] = `254`; +exports[`Path gas cost [ @skip-on-coverage ] 1`] = `438`; diff --git a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap index 8fd151b21..b873a70d7 100644 --- a/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap +++ b/src/periphery/test/__snapshots__/PoolAddress.spec.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PoolAddress #computeAddress gas cost [ @skip-on-coverage ] 1`] = `625`; +exports[`PoolAddress #computeAddress gas cost [ @skip-on-coverage ] 1`] = `673`; exports[`PoolAddress #computeAddress matches example from core repo 1`] = `"0x6C7bF9CdFBA917dAd9Ba260C0a2Abab05F44200e"`; diff --git a/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap b/src/periphery/test/__snapshots__/PositionValue.spec.ts.snap index 12dcf6860..a2107a0d0 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`] = `48350`; +exports[`PositionValue #fees when price is above the position range gas 1`] = `51801`; -exports[`PositionValue #fees when price is below the position range gas 1`] = `48416`; +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`] = `53919`; +exports[`PositionValue #fees when price is within the position range gas 1`] = `57326`; -exports[`PositionValue #principal gas 1`] = `23152`; +exports[`PositionValue #principal gas 1`] = `26316`; -exports[`PositionValue #total gas 1`] = `57010`; +exports[`PositionValue #total gas 1`] = `60417`; diff --git a/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap b/src/periphery/test/__snapshots__/QuoterV2.spec.ts.snap index f1b0678f4..5fa868d57 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`] = `156384`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 0 -> 2 1`] = `156167`; -exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156332`; +exports[`QuoterV2 quotes #quoteExactInputSingle gas [ @skip-on-coverage ] 2 -> 0 1`] = `156109`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `247726`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 0 -> 2 -> 1 1`] = `247457`; -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 initialized 1`] = `105794`; -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 0 tick starting tick not initialized 1`] = `90526`; -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 1 tick 1`] = `125890`; -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 tick 1`] = `156059`; -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 ] 0 -> 2 cross 2 where tick after is initialized 1`] = `125899`; -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 1 tick 1`] = `126227`; -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 ticks 1`] = `156670`; -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 -> 0 cross 2 where tick after is initialized 1`] = `156674`; -exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91433`; +exports[`QuoterV2 quotes #quoteExactOutput gas [ @skip-on-coverage ] 2 -> 1 1`] = `91417`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `91632`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 0 -> 1 1`] = `91513`; -exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `91649`; +exports[`QuoterV2 quotes #quoteExactOutputSingle gas [ @skip-on-coverage ] 1 -> 0 1`] = `91530`; diff --git a/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap b/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap index 249d0d811..2c9a615c5 100644 --- a/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap +++ b/src/periphery/test/__snapshots__/SwapRouter.spec.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SwapRouter bytecode size [ @skip-on-coverage ] 1`] = `12378`; +exports[`SwapRouter bytecode size [ @skip-on-coverage ] 1`] = `12653`; diff --git a/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap b/src/periphery/test/__snapshots__/V3Migrator.spec.ts.snap index 1e286f4bb..00144d0d4 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`] = `747042`; +exports[`V3Migrator #migrate gas [ @skip-on-coverage ] 1`] = `751887`; diff --git a/src/periphery/test/shared/completeFixture.ts b/src/periphery/test/shared/completeFixture.ts index 937e2c71e..bf7da8c1b 100644 --- a/src/periphery/test/shared/completeFixture.ts +++ b/src/periphery/test/shared/completeFixture.ts @@ -8,8 +8,12 @@ import { MockTimeSwapRouter, NonfungibleTokenPositionDescriptor, TestERC20, - IAlgebraFactory, + AlgebraFactory, + MockPluginFactory, + AlgebraCustomPoolEntryPoint, + CustomPoolDeployerTest, } from '../../typechain'; +import { ZERO_ADDRESS } from '../CallbackValidation.spec'; type TestERC20WithAddress = TestERC20 & { address_: string | undefined }; @@ -43,11 +47,13 @@ const DEFAULT_TOKENS_RATIONS_DATA: TokenRatioSortData[] = [ const completeFixture: () => Promise<{ wnative: IWNativeToken; - factory: IAlgebraFactory; + factory: AlgebraFactory; router: MockTimeSwapRouter; nft: MockTimeNonfungiblePositionManager; nftDescriptor: NonfungibleTokenPositionDescriptor; tokens: [TestERC20, TestERC20, TestERC20]; + customPoolDeployer: CustomPoolDeployerTest; + path: [string, string, string, string, string]; }> = async () => { const { wnative, factory, router } = await v3RouterFixture(); const tokenFactory = await ethers.getContractFactory('TestERC20'); @@ -67,6 +73,11 @@ const completeFixture: () => Promise<{ return tokenA.address_.toLowerCase() < tokenB.address_.toLowerCase() ? -1 : 1; }); + const pluginFactoryFactory = await ethers.getContractFactory('MockPluginFactory'); + const pluginFactory = await pluginFactoryFactory.deploy(factory) as any as MockPluginFactory; + + await factory.setDefaultPluginFactory(await pluginFactory.getAddress()); + const nftDescriptorLibraryFactory = await ethers.getContractFactory('NFTDescriptor'); const nftDescriptorLibrary = await nftDescriptorLibraryFactory.deploy(); const positionDescriptorFactory = await ethers.getContractFactory('NonfungibleTokenPositionDescriptor', { @@ -92,11 +103,32 @@ const completeFixture: () => Promise<{ await factory.poolDeployer() )) as any as MockTimeNonfungiblePositionManager; + const entryPointFactory = await ethers.getContractFactory("AlgebraCustomPoolEntryPoint"); + const entryPoint = await entryPointFactory.deploy(factory) as any as AlgebraCustomPoolEntryPoint; + + const customPoolDeployerFactory = await ethers.getContractFactory("CustomPoolDeployerTest"); + const customPoolDeployer = await customPoolDeployerFactory.deploy(entryPoint, ZERO_ADDRESS) as any as CustomPoolDeployerTest; + + let customPoolDeployerRole = await factory.CUSTOM_POOL_DEPLOYER() + let poolAdministratorRole = await factory.POOLS_ADMINISTRATOR_ROLE() + await factory.grantRole(customPoolDeployerRole, await entryPoint.getAddress()); + await factory.grantRole(poolAdministratorRole, await entryPoint.getAddress()); + + const path: [string, string, string, string, string] = [ + tokens[0].address_, + ZERO_ADDRESS, // deployer + tokens[1].address_, + await customPoolDeployer.getAddress(), // deployer + tokens[2].address_ + ] + return { wnative, factory, router, tokens, + customPoolDeployer, + path, nft, nftDescriptor: nftDescriptorProxied, }; diff --git a/src/periphery/test/shared/computePoolAddress.ts b/src/periphery/test/shared/computePoolAddress.ts index ad900cfc7..8390621b8 100644 --- a/src/periphery/test/shared/computePoolAddress.ts +++ b/src/periphery/test/shared/computePoolAddress.ts @@ -17,3 +17,18 @@ export function computePoolAddress(factoryAddress: string, [tokenA, tokenB]: [st const sanitizedInputs = `0x${create2Inputs.map((i) => i.slice(2)).join('')}`; return getAddress(`0x${keccak256(sanitizedInputs).slice(-40)}`); } + +export function computeCustomPoolAddress(factoryAddress: string, [tokenA, deployer, tokenB]: [string, string, string]): string { + const [token0, token1] = tokenA.toLowerCase() < tokenB.toLowerCase() ? [tokenA, tokenB] : [tokenB, tokenA]; + const constructorArgumentsEncoded = AbiCoder.defaultAbiCoder().encode(['address', 'address', 'address'], [deployer, token0, token1]); + const create2Inputs = [ + '0xff', + factoryAddress, + // salt + keccak256(constructorArgumentsEncoded), + // init code hash + POOL_BYTECODE_HASH, + ]; + const sanitizedInputs = `0x${create2Inputs.map((i) => i.slice(2)).join('')}`; + return getAddress(`0x${keccak256(sanitizedInputs).slice(-40)}`); +} diff --git a/src/periphery/test/shared/externalFixtures.ts b/src/periphery/test/shared/externalFixtures.ts index 07141a72b..eaedd7e3b 100644 --- a/src/periphery/test/shared/externalFixtures.ts +++ b/src/periphery/test/shared/externalFixtures.ts @@ -9,7 +9,7 @@ import { import { abi as FACTORY_V2_ABI, bytecode as FACTORY_V2_BYTECODE } from '@uniswap/v2-core/build/UniswapV2Factory.json'; import { ethers } from 'hardhat'; -import { IAlgebraFactory, IWNativeToken, MockTimeSwapRouter } from '../../typechain'; +import { AlgebraFactory, IWNativeToken, MockTimeSwapRouter } from '../../typechain'; import WNativeToken from '../contracts/WNativeToken.json'; import { getCreateAddress, ZeroAddress } from 'ethers'; diff --git a/src/periphery/test/shared/path.ts b/src/periphery/test/shared/path.ts index 2b5b7404b..5759d58ac 100644 --- a/src/periphery/test/shared/path.ts +++ b/src/periphery/test/shared/path.ts @@ -15,16 +15,20 @@ export function encodePath(path: string[]): string { return encoded.toLowerCase(); } -function decodeOne(tokenFeeToken: Buffer): [[string, string]] { +function decodeOne(tokenFeeToken: Buffer): [[string, string, string]] { // reads the first 20 bytes for the token address const tokenABuf = tokenFeeToken.slice(0, ADDR_SIZE); const tokenA = getAddress('0x' + tokenABuf.toString('hex')); + // reads the first 20 bytes for the deployer address + const deployerBuf = tokenFeeToken.slice(OFFSET, DATA_SIZE); + const deployer = getAddress('0x' + deployerBuf.toString('hex')); + // reads the next 20 bytes for the token address - const tokenBBuf = tokenFeeToken.slice(OFFSET, DATA_SIZE); + const tokenBBuf = tokenFeeToken.slice(DATA_SIZE, DATA_SIZE + ADDR_SIZE); const tokenB = getAddress('0x' + tokenBBuf.toString('hex')); - return [[tokenA, tokenB]]; + return [[tokenA, deployer, tokenB]]; } export function decodePath(path: string): [string[]] { @@ -34,10 +38,10 @@ export function decodePath(path: string): [string[]] { let i = 0; let finalToken: string = ''; while (data.length >= DATA_SIZE) { - const [[tokenA, tokenB]] = decodeOne(data); + const [[tokenA, deployer, tokenB]] = decodeOne(data); finalToken = tokenB; - tokens = [...tokens, tokenA]; - data = data.slice((i + 1) * OFFSET); + tokens = [...tokens, tokenA, deployer]; + data = data.slice((i + 1) * DATA_SIZE); i += 1; } tokens = [...tokens, finalToken]; diff --git a/src/periphery/test/shared/quoter.ts b/src/periphery/test/shared/quoter.ts index 0fd3ef205..267284406 100644 --- a/src/periphery/test/shared/quoter.ts +++ b/src/periphery/test/shared/quoter.ts @@ -3,21 +3,24 @@ import { MockTimeNonfungiblePositionManager } from '../../typechain'; import { FeeAmount, TICK_SPACINGS } from './constants'; import { encodePriceSqrt } from './encodePriceSqrt'; import { getMaxTick, getMinTick } from './ticks'; +import { ZERO_ADDRESS } from '../CallbackValidation.spec'; export async function createPool( nft: MockTimeNonfungiblePositionManager, wallet: Wallet, tokenAddressA: string, - tokenAddressB: string + tokenAddressB: string, + deployer: string ) { if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, deployer, encodePriceSqrt(1, 1)); const liquidityParams = { token0: tokenAddressA, token1: tokenAddressB, + deployer: deployer, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -40,11 +43,12 @@ export async function createPoolWithMultiplePositions( if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1)); const liquidityParams = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -60,6 +64,7 @@ export async function createPoolWithMultiplePositions( const liquidityParams2 = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: -60, tickUpper: 60, recipient: wallet.address, @@ -75,6 +80,7 @@ export async function createPoolWithMultiplePositions( const liquidityParams3 = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: -120, tickUpper: 120, recipient: wallet.address, @@ -97,11 +103,12 @@ export async function createPoolWithZeroTickInitialized( if (tokenAddressA.toLowerCase() > tokenAddressB.toLowerCase()) [tokenAddressA, tokenAddressB] = [tokenAddressB, tokenAddressA]; - await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, encodePriceSqrt(1, 1)); + await nft.createAndInitializePoolIfNecessary(tokenAddressA, tokenAddressB, ZERO_ADDRESS, encodePriceSqrt(1, 1)); const liquidityParams = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]), tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]), recipient: wallet.address, @@ -117,6 +124,7 @@ export async function createPoolWithZeroTickInitialized( const liquidityParams2 = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: 0, tickUpper: 60, recipient: wallet.address, @@ -132,6 +140,7 @@ export async function createPoolWithZeroTickInitialized( const liquidityParams3 = { token0: tokenAddressA, token1: tokenAddressB, + deployer: ZERO_ADDRESS, tickLower: -120, tickUpper: 0, recipient: wallet.address,