diff --git a/package.json b/package.json index 358d068..3e080e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@aperture_finance/uni-v3-lib", "description": "A suite of Solidity libraries that have been imported and rewritten from Uniswap's v3-core and v3-periphery", - "version": "1.2.2", + "version": "2.0.0", "author": "Aperture Finance", "homepage": "https://aperture.finance/", "license": "GPL-2.0-or-later", @@ -33,6 +33,8 @@ }, "dependencies": { "@openzeppelin/contracts": "^5.0.2", + "@pancakeswap/v3-core": "^1.0.2", + "@pancakeswap/v3-periphery": "^1.0.2", "@uniswap/v3-core": "^1.0.1", "@uniswap/v3-periphery": "^1.4.4", "solady": "^0.0.180" diff --git a/remappings.txt b/remappings.txt index 1c53a31..5ab7f22 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,4 +1,5 @@ @openzeppelin/=node_modules/@openzeppelin/ +@pancakeswap/=node_modules/@pancakeswap/ @uniswap/=node_modules/@uniswap/ forge-std/=lib/forge-std/src/ solady/=node_modules/solady/ diff --git a/src/CallbackValidationPancakeSwapV3.sol b/src/CallbackValidationPancakeSwapV3.sol new file mode 100644 index 0000000..31dd1c5 --- /dev/null +++ b/src/CallbackValidationPancakeSwapV3.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol"; +import "./PoolAddressPancakeSwapV3.sol"; + +/// @notice Provides validation for callbacks from PancakeSwapV3 Pools +/// @author Aperture Finance +/// @author Modified from PancakeSwapV3 (https://www.npmjs.com/package/@pancakeswap/v3-periphery?activeTab=code and look for contracts/libraries/CallbackValidation.sol) +library CallbackValidationPancakeSwapV3 { + /// @notice Returns the address of a valid PancakeSwapV3 Pool + /// @param deployer The contract address of the PancakeSwapV3 deployer + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The V3 pool contract address + function verifyCallback( + address deployer, + address tokenA, + address tokenB, + uint24 fee + ) internal view returns (IPancakeV3Pool pool) { + pool = IPancakeV3Pool(PoolAddressPancakeSwapV3.computeAddress(deployer, tokenA, tokenB, fee)); + require(msg.sender == address(pool)); + } + + /// @notice Returns the address of a valid PancakeSwapV3 Pool + /// @param deployer The contract address of the PancakeSwapV3 deployer + /// @param poolKey The identifying key of the V3 pool + /// @return pool The V3 pool contract address + function verifyCallback(address deployer, PoolKey memory poolKey) internal view returns (IPancakeV3Pool pool) { + pool = IPancakeV3Pool(PoolAddressPancakeSwapV3.computeAddressSorted(deployer, poolKey)); + require(msg.sender == address(pool)); + } + + /// @notice Returns the address of a valid PancakeSwapV3 Pool + /// @param deployer The contract address of the PancakeSwapV3 deployer + /// @param poolKey The abi encoded PoolKey of the V3 pool + /// @return pool The V3 pool contract address + function verifyCallbackCalldata(address deployer, bytes calldata poolKey) internal view returns (address pool) { + pool = PoolAddressPancakeSwapV3.computeAddressCalldata(deployer, poolKey); + require(msg.sender == pool); + } +} diff --git a/src/PoolAddress.sol b/src/PoolAddress.sol index 3e35d2c..e5e0e01 100644 --- a/src/PoolAddress.sol +++ b/src/PoolAddress.sol @@ -1,15 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.5.0; +import "./PoolKey.sol"; import "./TernaryLib.sol"; -/// @notice The identifying key of the pool -struct PoolKey { - address token0; - address token1; - uint24 fee; -} - /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee /// @author Aperture Finance /// @author Modified from Uniswap (https://github.com/uniswap/v3-periphery/blob/main/contracts/libraries/PoolAddress.sol) diff --git a/src/PoolAddressPancakeSwapV3.sol b/src/PoolAddressPancakeSwapV3.sol new file mode 100644 index 0000000..ad7cb3f --- /dev/null +++ b/src/PoolAddressPancakeSwapV3.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import "./PoolKey.sol"; +import "./TernaryLib.sol"; + +/// @title Provides functions for deriving a pool address from the deployer, tokens, and the fee +/// @author Aperture Finance +/// @author Modified from PancakeSwapV3 (https://www.npmjs.com/package/@pancakeswap/v3-periphery?activeTab=code and look for contracts/libraries/PoolAddress.sol) +/// @dev Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. +/// However, this is safe because "Note that you do not need to update the free memory pointer if there is no following +/// allocation, but you can only use memory starting from the current offset given by the free memory pointer." +/// according to https://docs.soliditylang.org/en/latest/assembly.html#memory-safety. +library PoolAddressPancakeSwapV3 { + bytes32 internal constant POOL_INIT_CODE_HASH = 0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2; + + /// @notice Returns PoolKey: the ordered tokens with the matched fee levels + /// @param tokenA The first token of a pool, unsorted + /// @param tokenB The second token of a pool, unsorted + /// @param fee The fee level of the pool + /// @return key The pool details with ordered token0 and token1 assignments + function getPoolKey(address tokenA, address tokenB, uint24 fee) internal pure returns (PoolKey memory key) { + (tokenA, tokenB) = TernaryLib.sort2(tokenA, tokenB); + /// @solidity memory-safe-assembly + assembly { + // Must inline this for best performance + mstore(key, tokenA) + mstore(add(key, 0x20), tokenB) + mstore(add(key, 0x40), fee) + } + } + + /// @notice Returns PoolKey: the ordered tokens with the matched fee levels + /// @param token0 The first token of a pool, already sorted + /// @param token1 The second token of a pool, already sorted + /// @param fee The fee level of the pool + /// @return key The pool details with ordered token0 and token1 assignments + function getPoolKeySorted(address token0, address token1, uint24 fee) internal pure returns (PoolKey memory key) { + /// @solidity memory-safe-assembly + assembly { + mstore(key, token0) + mstore(add(key, 0x20), token1) + mstore(add(key, 0x40), fee) + } + } + + /// @notice Deterministically computes the pool address given the deployer and PoolKey + /// @param deployer The PancakeSwapV3 deployer contract address + /// @param key The PoolKey + /// @return pool The contract address of the V3 pool + function computeAddress(address deployer, PoolKey memory key) internal pure returns (address pool) { + require(key.token0 < key.token1); + return computeAddressSorted(deployer, key); + } + + /// @notice Deterministically computes the pool address given the deployer and PoolKey + /// @dev Assumes PoolKey is sorted + /// @param deployer The PancakeSwapV3 deployer contract address + /// @param key The PoolKey + /// @return pool The contract address of the V3 pool + function computeAddressSorted(address deployer, PoolKey memory key) internal pure returns (address pool) { + /// @solidity memory-safe-assembly + assembly { + // Cache the free memory pointer. + let fmp := mload(0x40) + // abi.encodePacked(hex'ff', deployer, poolHash, POOL_INIT_CODE_HASH) + // Prefix the deployer address with 0xff. + mstore(0, or(deployer, 0xff0000000000000000000000000000000000000000)) + mstore(0x20, keccak256(key, 0x60)) + mstore(0x40, POOL_INIT_CODE_HASH) + // Compute the CREATE2 pool address and clean the upper bits. + pool := and(keccak256(0x0b, 0x55), 0xffffffffffffffffffffffffffffffffffffffff) + // Restore the free memory pointer. + mstore(0x40, fmp) + } + } + + /// @notice Deterministically computes the pool address given the deployer, tokens, and the fee + /// @param deployer The PancakeSwapV3 deployer contract address + /// @param tokenA One of the tokens in the pool, unsorted + /// @param tokenB The other token in the pool, unsorted + /// @param fee The fee tier of the pool + function computeAddress( + address deployer, + address tokenA, + address tokenB, + uint24 fee + ) internal pure returns (address pool) { + (tokenA, tokenB) = TernaryLib.sort2(tokenA, tokenB); + return computeAddressSorted(deployer, tokenA, tokenB, fee); + } + + /// @notice Deterministically computes the pool address given the deployer, tokens, and the fee + /// @dev Assumes tokens are sorted + /// @param deployer The PancakeSwapV3 deployer contract address + /// @param token0 The first token of a pool, already sorted + /// @param token1 The second token of a pool, already sorted + /// @param fee The fee tier of the pool + function computeAddressSorted( + address deployer, + address token0, + address token1, + uint24 fee + ) internal pure returns (address pool) { + /// @solidity memory-safe-assembly + assembly { + // Cache the free memory pointer. + let fmp := mload(0x40) + // Hash the pool key. + mstore(0, token0) + mstore(0x20, token1) + mstore(0x40, fee) + let poolHash := keccak256(0, 0x60) + // abi.encodePacked(hex'ff', deployer, poolHash, POOL_INIT_CODE_HASH) + // Prefix the deployer address with 0xff. + mstore(0, or(deployer, 0xff0000000000000000000000000000000000000000)) + mstore(0x20, poolHash) + mstore(0x40, POOL_INIT_CODE_HASH) + // Compute the CREATE2 pool address and clean the upper bits. + pool := and(keccak256(0x0b, 0x55), 0xffffffffffffffffffffffffffffffffffffffff) + // Restore the free memory pointer. + mstore(0x40, fmp) + } + } + + /// @notice Deterministically computes the pool address given the deployer and PoolKey + /// @dev Uses PoolKey in calldata and assumes PoolKey is sorted + /// @param deployer The PancakeSwapV3 deployer contract address + /// @param key The abi encoded PoolKey of the V3 pool + /// @return pool The contract address of the V3 pool + function computeAddressCalldata(address deployer, bytes calldata key) internal pure returns (address pool) { + /// @solidity memory-safe-assembly + assembly { + // Cache the free memory pointer. + let fmp := mload(0x40) + // Hash the pool key. + calldatacopy(0, key.offset, 0x60) + let poolHash := keccak256(0, 0x60) + // abi.encodePacked(hex'ff', deployer, poolHash, POOL_INIT_CODE_HASH) + // Prefix the deployer address with 0xff. + mstore(0, or(deployer, 0xff0000000000000000000000000000000000000000)) + mstore(0x20, poolHash) + mstore(0x40, POOL_INIT_CODE_HASH) + // Compute the CREATE2 pool address and clean the upper bits. + pool := and(keccak256(0x0b, 0x55), 0xffffffffffffffffffffffffffffffffffffffff) + // Restore the free memory pointer. + mstore(0x40, fmp) + } + } +} diff --git a/src/PoolKey.sol b/src/PoolKey.sol new file mode 100644 index 0000000..47f92e9 --- /dev/null +++ b/src/PoolKey.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @notice The identifying key of a liquidity pool +struct PoolKey { + address token0; + address token1; + uint24 fee; +} diff --git a/src/test/PoolAddressPancakeSwapV3Test.sol b/src/test/PoolAddressPancakeSwapV3Test.sol new file mode 100644 index 0000000..9de6bab --- /dev/null +++ b/src/test/PoolAddressPancakeSwapV3Test.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +import "@pancakeswap/v3-periphery/contracts/libraries/PoolAddress.sol"; +import "./interfaces/IPoolAddress.sol"; + +/// @dev Expose internal functions to test the PoolAddress library. +contract PoolAddressPancakeSwapV3Test is IPoolAddress { + function getPoolKey( + address tokenA, + address tokenB, + uint24 fee + ) external pure override returns (PoolKey memory key) { + PoolAddress.PoolKey memory _key = PoolAddress.getPoolKey(tokenA, tokenB, fee); + /// @solidity memory-safe-assembly + assembly { + key := _key + } + } + + function computeAddress(address deployer, PoolKey memory key) external pure override returns (address pool) { + PoolAddress.PoolKey memory _key; + /// @solidity memory-safe-assembly + assembly { + _key := key + } + return PoolAddress.computeAddress(deployer, _key); + } +} diff --git a/src/test/PoolAddressTest.sol b/src/test/PoolAddressTest.sol index 0759aa4..7872bf2 100644 --- a/src/test/PoolAddressTest.sol +++ b/src/test/PoolAddressTest.sol @@ -11,7 +11,7 @@ contract PoolAddressTest is IPoolAddress { address tokenA, address tokenB, uint24 fee - ) external pure override returns (IPoolKey memory key) { + ) external pure override returns (PoolKey memory key) { PoolAddress.PoolKey memory _key = PoolAddress.getPoolKey(tokenA, tokenB, fee); /// @solidity memory-safe-assembly assembly { @@ -19,7 +19,7 @@ contract PoolAddressTest is IPoolAddress { } } - function computeAddress(address factory, IPoolKey memory key) external pure override returns (address pool) { + function computeAddress(address factory, PoolKey memory key) external pure override returns (address pool) { PoolAddress.PoolKey memory _key; /// @solidity memory-safe-assembly assembly { diff --git a/src/test/interfaces/IPoolAddress.sol b/src/test/interfaces/IPoolAddress.sol index e3da8e5..b9c39cc 100644 --- a/src/test/interfaces/IPoolAddress.sol +++ b/src/test/interfaces/IPoolAddress.sol @@ -2,14 +2,10 @@ pragma solidity >=0.5.0; pragma abicoder v2; -interface IPoolAddress { - struct IPoolKey { - address token0; - address token1; - uint24 fee; - } +import "../../PoolKey.sol"; - function getPoolKey(address tokenA, address tokenB, uint24 fee) external pure returns (IPoolKey memory); +interface IPoolAddress { + function getPoolKey(address tokenA, address tokenB, uint24 fee) external pure returns (PoolKey memory); - function computeAddress(address factory, IPoolKey memory key) external pure returns (address pool); + function computeAddress(address factory, PoolKey memory key) external pure returns (address pool); } diff --git a/test/Base.t.sol b/test/Base.t.sol index d583900..5e0127f 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IPancakeV3Factory} from "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Factory.sol"; +import {IPancakeV3Pool} from "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol"; import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import {INonfungiblePositionManager} from "src/interfaces/INonfungiblePositionManager.sol"; @@ -12,22 +14,39 @@ import {TickMath} from "src/TickMath.sol"; /// @dev Base test class for all tests. abstract contract BaseTest is Test { - IUniswapV3Factory internal constant factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); - INonfungiblePositionManager internal constant npm = - INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + address internal factory; + INonfungiblePositionManager internal npm; address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address internal token0; address internal token1; - uint24 internal constant fee = 3000; + uint24 internal fee; address internal pool; int24 internal tickSpacing; + // `dex` is used to determine which DEX to use for the test. + // The default value is `UniswapV3` as that is the zero value. + BaseTest.DEX internal dex; + + enum DEX { + UniswapV3, + PancakeSwapV3 + } function setUp() public virtual {} function createFork() internal { vm.createSelectFork("mainnet", 17000000); - pool = factory.getPool(WETH, USDC, fee); + if (dex == DEX.UniswapV3) { + factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + npm = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + fee = 3000; + pool = IUniswapV3Factory(factory).getPool(WETH, USDC, fee); + } else { + factory = 0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865; + npm = INonfungiblePositionManager(0x46A15B0b27311cedF172AB29E4f4766fbE7F4364); + fee = 500; + pool = IPancakeV3Factory(factory).getPool(WETH, USDC, fee); + } tickSpacing = IUniswapV3Pool(pool).tickSpacing(); token0 = IUniswapV3Pool(pool).token0(); token1 = IUniswapV3Pool(pool).token1(); @@ -37,16 +56,6 @@ abstract contract BaseTest is Test { vm.label(pool, "pool"); } - function currentTick() internal view returns (int24 tick) { - (, tick, , , , , ) = IUniswapV3Pool(pool).slot0(); - } - - /// @dev Normalize tick to align with tick spacing - function matchSpacing(int24 tick) internal view returns (int24) { - int24 _tickSpacing = tickSpacing; - return TickBitmap.compress(tick, _tickSpacing) * _tickSpacing; - } - function boundUint160(uint160 x) internal pure returns (uint160) { return uint160(bound(x, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO)); } @@ -66,4 +75,18 @@ abstract contract BaseTest is Test { function pseudoRandomInt128(uint256 seed) internal pure returns (int128) { return int128(int256(pseudoRandom(seed))); } + + /// @dev Normalize tick to align with tick spacing + function matchSpacing(int24 tick) internal view returns (int24) { + int24 _tickSpacing = tickSpacing; + return TickBitmap.compress(tick, _tickSpacing) * _tickSpacing; + } + + function currentTick() internal view returns (int24 tick) { + if (dex == DEX.UniswapV3) { + (, tick, , , , , ) = IUniswapV3Pool(pool).slot0(); + } else { + (, tick, , , , , ) = IPancakeV3Pool(pool).slot0(); + } + } } diff --git a/test/BitMath.t.sol b/test/BitMath.t.sol index 7309dbd..a228e72 100644 --- a/test/BitMath.t.sol +++ b/test/BitMath.t.sol @@ -16,12 +16,12 @@ contract BitMathWrapper is IBitMath { } /// @title Test contract for BitMath -contract BitMathTest is BaseTest { +contract BitMathTest is Test { // Wrapper that exposes the original BitMath library. IBitMath internal ogWrapper; BitMathWrapper internal wrapper; - function setUp() public override { + function setUp() public { ogWrapper = IBitMath(deployCode("BitMathTest.sol")); wrapper = new BitMathWrapper(); } diff --git a/test/FullMath.t.sol b/test/FullMath.t.sol index 57a509c..c2b2c41 100644 --- a/test/FullMath.t.sol +++ b/test/FullMath.t.sol @@ -8,11 +8,11 @@ import {FullMath} from "src/FullMath.sol"; import "./Base.t.sol"; /// @dev Tests for FullMath -contract FullMathTest is BaseTest { +contract FullMathTest is Test { // Wrapper that exposes the original FullMath library. IFullMath internal wrapper; - function setUp() public override { + function setUp() public { wrapper = IFullMath(deployCode("FullMathTest.sol")); } diff --git a/test/LiquidityMath.t.sol b/test/LiquidityMath.t.sol index a8c3e7c..977b881 100644 --- a/test/LiquidityMath.t.sol +++ b/test/LiquidityMath.t.sol @@ -12,12 +12,12 @@ contract LiquidityMathWrapper is ILiquidityMath { } /// @dev Tests for FullMath -contract LiquidityMathTest is BaseTest { +contract LiquidityMathTest is Test { // Wrapper that exposes the original LiquidityMath library. ILiquidityMath internal ogWrapper; LiquidityMathWrapper internal wrapper; - function setUp() public override { + function setUp() public { ogWrapper = ILiquidityMath(deployCode("LiquidityMathTest.sol")); wrapper = new LiquidityMathWrapper(); } diff --git a/test/NPMCaller.t.sol b/test/NPMCaller.t.sol index e9cf60b..67fdb85 100644 --- a/test/NPMCaller.t.sol +++ b/test/NPMCaller.t.sol @@ -123,7 +123,7 @@ contract NPMCallerTest is BaseTest { address internal user; uint256 internal pk; - function setUp() public override { + function setUp() public virtual override { createFork(); npmCaller = new NPMCallerWrapper(npm); PERMIT_TYPEHASH = npm.PERMIT_TYPEHASH(); @@ -152,14 +152,14 @@ contract NPMCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_BalanceOf(address owner) public view { + function testFuzz_BalanceOf(address owner) public view virtual { vm.assume(owner != address(0)); assertEq(npmCaller.balanceOf(owner), npm.balanceOf(owner)); } /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_TokenOfOwnerByIndex(uint256 tokenId) public view { + function testFuzz_TokenOfOwnerByIndex(uint256 tokenId) public view virtual { tokenId = bound(tokenId, 1, 10000); try npm.ownerOf(tokenId) returns (address owner) { uint256 balance = npmCaller.balanceOf(owner); @@ -177,12 +177,12 @@ contract NPMCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_OwnerOf(uint256 tokenId) public { + function testFuzz_OwnerOf(uint256 tokenId) public virtual { tokenId = bound(tokenId, 1, 10000); try npm.ownerOf(tokenId) returns (address owner) { assertEq(owner, npmCaller.ownerOf(tokenId), "ownerOf"); - } catch (bytes memory reason) { - vm.expectRevert(reason); + } catch { + vm.expectRevert("ERC721: owner query for nonexistent token"); npmCaller.ownerOf(tokenId); } } @@ -194,12 +194,12 @@ contract NPMCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_GetApproved(uint256 tokenId) public { + function testFuzz_GetApproved(uint256 tokenId) public virtual { tokenId = bound(tokenId, 1, 10000); try npm.getApproved(tokenId) returns (address operator) { assertEq(operator, npmCaller.getApproved(tokenId), "getApproved"); - } catch (bytes memory reason) { - vm.expectRevert(reason); + } catch { + vm.expectRevert("ERC721: approved query for nonexistent token"); npmCaller.getApproved(tokenId); } } @@ -229,7 +229,7 @@ contract NPMCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_IsApprovedForAll(uint256 tokenId) public { + function testFuzz_IsApprovedForAll(uint256 tokenId) public virtual { tokenId = bound(tokenId, 1, 10000); try npmCaller.ownerOf(tokenId) returns (address owner) { assertEq(npmCaller.isApprovedForAll(owner, address(this)), false, "is approved"); @@ -253,7 +253,7 @@ contract NPMCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_PositionsFull(uint256 tokenId) public view { + function testFuzz_PositionsFull(uint256 tokenId) public view virtual { tokenId = bound(tokenId, 1, 10000); try npmCaller.positionsFull(tokenId) returns (PositionFull memory pos) { (uint96 nonce, , address token0, , , int24 tickLower, , uint128 liquidity, , , , uint128 tokensOwed1) = npm @@ -270,7 +270,7 @@ contract NPMCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_Positions(uint256 tokenId) public view { + function testFuzz_Positions(uint256 tokenId) public view virtual { tokenId = bound(tokenId, 1, 10000); try npmCaller.positions(tokenId) returns (Position memory pos) { ( @@ -387,3 +387,54 @@ contract NPMCallerTest is BaseTest { assertEq(npm.getApproved(tokenId), address(_npmCaller), "not approved"); } } + +contract NPMCallerPCSTest is NPMCallerTest { + function setUp() public override { + dex = DEX.PancakeSwapV3; + super.setUp(); + } + + // The following functions are overridden trivially so the in-line fuzz configuration is applied. + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_BalanceOf(address owner) public view override { + super.testFuzz_BalanceOf(owner); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_GetApproved(uint256 tokenId) public override { + super.testFuzz_GetApproved(tokenId); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_IsApprovedForAll(uint256 tokenId) public override { + super.testFuzz_IsApprovedForAll(tokenId); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_OwnerOf(uint256 tokenId) public override { + super.testFuzz_OwnerOf(tokenId); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_Positions(uint256 tokenId) public view override { + super.testFuzz_Positions(tokenId); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_PositionsFull(uint256 tokenId) public view override { + super.testFuzz_PositionsFull(tokenId); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_TokenOfOwnerByIndex(uint256 tokenId) public view override { + super.testFuzz_TokenOfOwnerByIndex(tokenId); + } +} diff --git a/test/PoolAddress.t.sol b/test/PoolAddress.t.sol index 6c4bb94..0cf3022 100644 --- a/test/PoolAddress.t.sol +++ b/test/PoolAddress.t.sol @@ -3,28 +3,50 @@ pragma solidity ^0.8.0; import {IPoolAddress} from "src/test/interfaces/IPoolAddress.sol"; import {CallbackValidation} from "src/CallbackValidation.sol"; +import {CallbackValidationPancakeSwapV3} from "src/CallbackValidationPancakeSwapV3.sol"; import {PoolAddress, PoolKey} from "src/PoolAddress.sol"; +import {PoolAddressPancakeSwapV3} from "src/PoolAddressPancakeSwapV3.sol"; import "./Base.t.sol"; -/// @dev Expose internal functions to test the PoolAddress library. -contract PoolAddressWrapper is IPoolAddress { - function getPoolKey( +interface IPoolAddressWrapper is IPoolAddress { + function computeAddress( + address factory, address tokenA, address tokenB, uint24 fee - ) external pure returns (IPoolAddress.IPoolKey memory key) { - PoolKey memory _key = PoolAddress.getPoolKey(tokenA, tokenB, fee); - assembly ("memory-safe") { - key := _key - } + ) external pure returns (address pool); + + function computeAddressCalldata(address factory, bytes calldata key) external pure returns (address); + + function verifyCallback( + address factory, + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + function verifyCallback(address factory, PoolKey memory poolKey) external view returns (address pool); + + function verifyCallbackCalldata(address factory, bytes calldata poolKey) external view returns (address pool); +} + +/// @dev Expose internal functions to test the PoolAddress library. +contract PoolAddressWrapper is IPoolAddressWrapper { + function getPoolKey(address tokenA, address tokenB, uint24 fee) external pure returns (PoolKey memory key) { + key = PoolAddress.getPoolKey(tokenA, tokenB, fee); + } + + function computeAddress(address factory, PoolKey memory key) external pure returns (address pool) { + return PoolAddress.computeAddress(factory, key); } - function computeAddress(address factory, IPoolAddress.IPoolKey memory key) external pure returns (address pool) { - PoolKey memory _key; - assembly ("memory-safe") { - _key := key - } - return PoolAddress.computeAddress(factory, _key); + function computeAddress( + address factory, + address tokenA, + address tokenB, + uint24 fee + ) external pure returns (address pool) { + return PoolAddress.computeAddress(factory, tokenA, tokenB, fee); } function computeAddressCalldata(address factory, bytes calldata key) external pure returns (address) { @@ -49,13 +71,55 @@ contract PoolAddressWrapper is IPoolAddress { } } +/// @dev Expose internal functions to test the PoolAddressPancakeSwapV3 library. +contract PoolAddressPancakeSwapV3Wrapper is IPoolAddressWrapper { + function getPoolKey(address tokenA, address tokenB, uint24 fee) external pure returns (PoolKey memory key) { + key = PoolAddressPancakeSwapV3.getPoolKey(tokenA, tokenB, fee); + } + + function computeAddress(address deployer, PoolKey memory key) external pure returns (address pool) { + return PoolAddressPancakeSwapV3.computeAddress(deployer, key); + } + + function computeAddress( + address factory, + address tokenA, + address tokenB, + uint24 fee + ) external pure returns (address pool) { + return PoolAddressPancakeSwapV3.computeAddress(factory, tokenA, tokenB, fee); + } + + function computeAddressCalldata(address deployer, bytes calldata key) external pure returns (address) { + return PoolAddressPancakeSwapV3.computeAddressCalldata(deployer, key); + } + + function verifyCallback( + address deployer, + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool) { + return address(CallbackValidationPancakeSwapV3.verifyCallback(deployer, tokenA, tokenB, fee)); + } + + function verifyCallback(address deployer, PoolKey memory poolKey) external view returns (address pool) { + return address(CallbackValidationPancakeSwapV3.verifyCallback(deployer, poolKey)); + } + + function verifyCallbackCalldata(address deployer, bytes calldata poolKey) external view returns (address pool) { + return CallbackValidationPancakeSwapV3.verifyCallbackCalldata(deployer, poolKey); + } +} + /// @dev Test contract for CallbackValidation and PoolAddress. contract PoolAddressTest is BaseTest { // Wrapper that exposes the original PoolAddress library. IPoolAddress internal ogWrapper; - PoolAddressWrapper internal wrapper; + IPoolAddressWrapper internal wrapper; - function setUp() public override { + function setUp() public virtual override { + factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; ogWrapper = IPoolAddress(deployCode("PoolAddressTest.sol")); wrapper = new PoolAddressWrapper(); } @@ -65,19 +129,19 @@ contract PoolAddressTest is BaseTest { vm.assume(tokenA != tokenB); if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); assertEq( - PoolAddress.computeAddress(address(factory), tokenA, tokenB, fee), - ogWrapper.computeAddress(address(factory), ogWrapper.getPoolKey(tokenA, tokenB, fee)) + wrapper.computeAddress(factory, tokenA, tokenB, fee), + ogWrapper.computeAddress(factory, ogWrapper.getPoolKey(tokenA, tokenB, fee)) ); } /// @notice Benchmark the gas cost of `computeAddress`. function testGas_ComputeAddress() public view { - wrapper.computeAddress(address(factory), wrapper.getPoolKey(WETH, USDC, fee)); + wrapper.computeAddress(factory, wrapper.getPoolKey(WETH, USDC, fee)); } /// @notice Benchmark the gas cost of `computeAddress` from the original library. function testGas_ComputeAddress_Og() public view { - ogWrapper.computeAddress(address(factory), ogWrapper.getPoolKey(WETH, USDC, fee)); + ogWrapper.computeAddress(factory, ogWrapper.getPoolKey(WETH, USDC, fee)); } /// @notice Test `computeAddress` against the original Uniswap library. @@ -85,8 +149,8 @@ contract PoolAddressTest is BaseTest { vm.assume(tokenA != tokenB); if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); assertEq( - PoolAddress.computeAddress(address(factory), PoolAddress.getPoolKey(tokenA, tokenB, fee)), - ogWrapper.computeAddress(address(factory), ogWrapper.getPoolKey(tokenA, tokenB, fee)) + wrapper.computeAddress(factory, wrapper.getPoolKey(tokenA, tokenB, fee)), + ogWrapper.computeAddress(factory, ogWrapper.getPoolKey(tokenA, tokenB, fee)) ); } @@ -95,8 +159,8 @@ contract PoolAddressTest is BaseTest { vm.assume(tokenA != tokenB); if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); assertEq( - wrapper.computeAddressCalldata(address(factory), abi.encode(PoolAddress.getPoolKey(tokenA, tokenB, fee))), - PoolAddress.computeAddress(address(factory), tokenA, tokenB, fee) + wrapper.computeAddressCalldata(factory, abi.encode(wrapper.getPoolKey(tokenA, tokenB, fee))), + wrapper.computeAddress(factory, tokenA, tokenB, fee) ); } @@ -104,20 +168,29 @@ contract PoolAddressTest is BaseTest { function testFuzz_VerifyCallback(address tokenA, address tokenB, uint24 fee) public { vm.assume(tokenA != tokenB); if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); - PoolKey memory key = PoolAddress.getPoolKey(tokenA, tokenB, fee); - address pool = PoolAddress.computeAddress(address(factory), key); + PoolKey memory key = wrapper.getPoolKey(tokenA, tokenB, fee); + address pool = wrapper.computeAddress(factory, key); vm.startPrank(pool); - wrapper.verifyCallback(address(factory), tokenA, tokenB, fee); - wrapper.verifyCallback(address(factory), key); + wrapper.verifyCallback(factory, tokenA, tokenB, fee); + wrapper.verifyCallback(factory, key); } /// @notice Test `verifyCallbackCalldata` against other implementation. function testFuzz_VerifyCallbackCalldata(address tokenA, address tokenB, uint24 fee) public { vm.assume(tokenA != tokenB); if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); - PoolKey memory key = PoolAddress.getPoolKey(tokenA, tokenB, fee); - address pool = PoolAddress.computeAddress(address(factory), key); + PoolKey memory key = wrapper.getPoolKey(tokenA, tokenB, fee); + address pool = wrapper.computeAddress(factory, key); vm.prank(pool); - wrapper.verifyCallbackCalldata(address(factory), abi.encode(key)); + wrapper.verifyCallbackCalldata(factory, abi.encode(key)); + } +} + +contract PoolAddressPCSTest is PoolAddressTest { + function setUp() public override { + // This is the PancakeSwap V3 *deployer* contract address as this is used instead of the factory in pool address computation. + factory = 0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9; + ogWrapper = IPoolAddress(deployCode("PoolAddressPancakeSwapV3Test.sol")); + wrapper = new PoolAddressPancakeSwapV3Wrapper(); } } diff --git a/test/PoolCaller.t.sol b/test/PoolCaller.t.sol index 5c39658..e42c3f7 100644 --- a/test/PoolCaller.t.sol +++ b/test/PoolCaller.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import "@pancakeswap/v3-core/contracts/interfaces/IPancakeV3Pool.sol"; import "src/PoolCaller.sol"; import "./Base.t.sol"; @@ -30,7 +31,7 @@ contract PoolCallerTest is BaseTest { PoolCallerWrapper internal poolCaller; V3PoolCallee internal poolCallee; - function setUp() public override { + function setUp() public virtual override { createFork(); poolCaller = new PoolCallerWrapper(pool); poolCallee = V3PoolCallee.wrap(pool); @@ -55,21 +56,21 @@ contract PoolCallerTest is BaseTest { } /// @dev Ensure that the swap is successful - function assertSwapSuccess(bool zeroForOne, uint256 amountOut) internal { + function assertSwapSuccess(bool zeroForOne, uint256 amountOut) internal view { (address tokenIn, address tokenOut) = zeroForOne ? (token0, token1) : (token1, token0); assertEq(IERC20(tokenIn).balanceOf(address(this)), 0, "amountIn not exhausted"); assertEq(IERC20(tokenOut).balanceOf(address(this)), amountOut, "amountOut mismatch"); } - function test_Fee() public { + function test_Fee() public view { assertEq(IUniswapV3Pool(pool).fee(), poolCallee.fee(), "fee"); } - function test_TickSpacing() public { + function test_TickSpacing() public view { assertEq(IUniswapV3Pool(pool).tickSpacing(), poolCallee.tickSpacing(), "tickSpacing"); } - function test_Slot0() public { + function test_Slot0() public virtual { ( uint160 sqrtPriceX96, int24 tick, @@ -97,14 +98,14 @@ contract PoolCallerTest is BaseTest { assertEq(unlocked, unlockedAsm, "unlocked"); } - function test_SqrtPriceX96AndTick() public { + function test_SqrtPriceX96AndTick() public virtual { (uint160 sqrtPriceX96, int24 tick, , , , , ) = IUniswapV3Pool(pool).slot0(); (uint160 sqrtPriceX96Asm, int24 tickAsm) = V3PoolCallee.wrap(pool).sqrtPriceX96AndTick(); assertEq(sqrtPriceX96, sqrtPriceX96Asm, "sqrtPriceX96"); assertEq(tick, tickAsm, "tick"); } - function test_FeeGrowthGlobal0X128() public { + function test_FeeGrowthGlobal0X128() public view { assertEq( IUniswapV3Pool(pool).feeGrowthGlobal0X128(), poolCallee.feeGrowthGlobal0X128(), @@ -112,7 +113,7 @@ contract PoolCallerTest is BaseTest { ); } - function test_FeeGrowthGlobal1X128() public { + function test_FeeGrowthGlobal1X128() public view { assertEq( IUniswapV3Pool(pool).feeGrowthGlobal1X128(), poolCallee.feeGrowthGlobal1X128(), @@ -120,20 +121,20 @@ contract PoolCallerTest is BaseTest { ); } - function test_ProtocolFees() public { + function test_ProtocolFees() public view { (uint128 token0Fee, uint128 token1Fee) = IUniswapV3Pool(pool).protocolFees(); (uint128 token0FeeAsm, uint128 token1FeeAsm) = poolCallee.protocolFees(); assertEq(token0Fee, token0FeeAsm, "token0Fee"); assertEq(token1Fee, token1FeeAsm, "token1Fee"); } - function test_Liquidity() public { + function test_Liquidity() public view { assertEq(IUniswapV3Pool(pool).liquidity(), poolCallee.liquidity(), "liquidity"); } /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_Ticks(int24 tick) public { + function testFuzz_Ticks(int24 tick) public virtual { ( uint128 liquidityGross, int128 liquidityNet, @@ -161,7 +162,7 @@ contract PoolCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_LiquidityNet(int24 tick) public { + function testFuzz_LiquidityNet(int24 tick) public virtual { (, int128 liquidityNet, , , , , , ) = IUniswapV3Pool(pool).ticks(tick); int128 liquidityNetAsm = poolCallee.liquidityNet(tick); assertEq(liquidityNet, liquidityNetAsm, "liquidityNet"); @@ -169,13 +170,13 @@ contract PoolCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_TickBitmap(int16 wordPos) public { + function testFuzz_TickBitmap(int16 wordPos) public virtual { assertEq(IUniswapV3Pool(pool).tickBitmap(wordPos), poolCallee.tickBitmap(wordPos), "tickBitmap"); } /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_Positions(bytes32 key) public { + function testFuzz_Positions(bytes32 key) public virtual { ( uint128 _liquidity, uint256 feeGrowthInside0LastX128, @@ -193,7 +194,7 @@ contract PoolCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_Observations(uint256 index) public { + function testFuzz_Observations(uint256 index) public virtual { (, , uint16 observationIndex, , , , ) = poolCallee.slot0(); index = bound(index, 0, observationIndex - 1); ( @@ -233,7 +234,7 @@ contract PoolCallerTest is BaseTest { /// forge-config: default.fuzz.runs = 16 /// forge-config: ci.fuzz.runs = 16 - function testFuzz_Swap(bool zeroForOne, uint256 amountSpecified, bytes memory data) public { + function testFuzz_Swap(bool zeroForOne, uint256 amountSpecified, bytes memory data) public virtual { amountSpecified = prepSwap(zeroForOne, amountSpecified); (int256 amount0, int256 amount1) = poolCallee.swap( address(this), @@ -255,3 +256,91 @@ contract PoolCallerTest is BaseTest { poolCaller.swap(address(this), true, int256(1), TickMath.MIN_SQRT_RATIO, new bytes(0)); } } + +contract PoolCallerPCSTest is PoolCallerTest { + function setUp() public override { + dex = DEX.PancakeSwapV3; + createFork(); + super.setUp(); + } + + // The following functions are overridden trivially so the in-line fuzz configuration is applied. + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_LiquidityNet(int24 tick) public override { + super.testFuzz_LiquidityNet(tick); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_Positions(bytes32 key) public override { + super.testFuzz_Positions(key); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_Swap(bool zeroForOne, uint256 amountSpecified, bytes memory data) public override { + super.testFuzz_Swap(zeroForOne, amountSpecified, data); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_TickBitmap(int16 wordPos) public override { + super.testFuzz_TickBitmap(wordPos); + } + + /// forge-config: default.fuzz.runs = 16 + /// forge-config: ci.fuzz.runs = 16 + function testFuzz_Ticks(int24 tick) public override { + super.testFuzz_Ticks(tick); + } + + /// forge-config: default.fuzz.runs = 1 + /// forge-config: ci.fuzz.runs = 1 + function testFuzz_Observations(uint256 index) public override { + // Skip this test as the PancakeSwap V3 pool did not have any observations at the fork block. + } + + /// @dev Note that PancakeSwap V3 changed the slot0 struct, specifically changed `feeProtocol` from uint8 to uint32. + // Therefore, `poolCallee.slot0()` is not able to return the correct value for `feeProtocol` or `unlocked`. + function test_Slot0() public view override { + ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + , + + ) = IPancakeV3Pool(pool).slot0(); + ( + uint160 sqrtPriceX96Asm, + int24 tickAsm, + uint16 observationIndexAsm, + uint16 observationCardinalityAsm, + uint16 observationCardinalityNextAsm, + , + + ) = poolCallee.slot0(); + assertEq(sqrtPriceX96, sqrtPriceX96Asm, "sqrtPriceX96"); + assertEq(tick, tickAsm, "tick"); + assertEq(observationIndex, observationIndexAsm, "observationIndex"); + assertEq(observationCardinality, observationCardinalityAsm, "observationCardinality"); + assertEq(observationCardinalityNext, observationCardinalityNextAsm, "observationCardinalityNext"); + } + + function test_SqrtPriceX96AndTick() public view override { + (uint160 sqrtPriceX96, int24 tick, , , , , ) = IPancakeV3Pool(pool).slot0(); + (uint160 sqrtPriceX96Asm, int24 tickAsm) = V3PoolCallee.wrap(pool).sqrtPriceX96AndTick(); + assertEq(sqrtPriceX96, sqrtPriceX96Asm, "sqrtPriceX96"); + assertEq(tick, tickAsm, "tick"); + } + + /// @dev Pay pool to finish swap + using SafeTransferLib for address; + function pancakeV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external { + if (amount0Delta > 0) token0.safeTransfer(pool, uint256(amount0Delta)); + if (amount1Delta > 0) token1.safeTransfer(pool, uint256(amount1Delta)); + } +} diff --git a/test/TickBitmap.t.sol b/test/TickBitmap.t.sol index a6e81f6..0e07713 100644 --- a/test/TickBitmap.t.sol +++ b/test/TickBitmap.t.sol @@ -34,7 +34,7 @@ contract TickBitmapTest is BaseTest { ITickBitmap internal ogWrapper; TickBitmapWrapper internal wrapper; - function setUp() public override { + function setUp() public virtual override { ogWrapper = ITickBitmap(deployCode("TickBitmapTest.sol")); wrapper = new TickBitmapWrapper(); } @@ -130,11 +130,12 @@ contract TickBitmapTest is BaseTest { /// @notice Test nextInitializedTickWithinOneWord in a fork function test_NextInitializedTickWithinOneWord_LTE() public { createFork(); + uint256 numIters = dex == DEX.PancakeSwapV3 ? 10 : 256; int24 tick = currentTick(); int16 wordPos = type(int16).min; uint256 tickWord; unchecked { - for (uint256 counter; counter < 256; ++counter) { + for (uint256 counter; counter < numIters; ++counter) { (tick, , wordPos, tickWord) = TickBitmap.nextInitializedTickWithinOneWord( V3PoolCallee.wrap(pool), tick - 1, @@ -150,11 +151,12 @@ contract TickBitmapTest is BaseTest { /// @notice Test nextInitializedTickWithinOneWord in a fork function test_NextInitializedTickWithinOneWord_GT() public { createFork(); + uint256 numIters = dex == DEX.PancakeSwapV3 ? 10 : 256; int24 tick = currentTick(); int16 wordPos = type(int16).min; uint256 tickWord; unchecked { - for (uint256 counter; counter < 256; ++counter) { + for (uint256 counter; counter < numIters; ++counter) { (tick, , wordPos, tickWord) = TickBitmap.nextInitializedTickWithinOneWord( V3PoolCallee.wrap(pool), tick, @@ -170,11 +172,12 @@ contract TickBitmapTest is BaseTest { /// @notice Test nextInitializedTick in a fork function test_NextInitializedTick_LTE() public { createFork(); + uint256 numIters = dex == DEX.PancakeSwapV3 ? 10 : 256; int24 tick = currentTick(); bool initialized; int16 wordPos = type(int16).min; uint256 tickWord; - for (uint256 counter; counter < 256; ) { + for (uint256 counter; counter < numIters; ) { (tick, initialized, wordPos, tickWord) = TickBitmap.nextInitializedTickWithinOneWord( V3PoolCallee.wrap(pool), tick - 1, @@ -192,7 +195,7 @@ contract TickBitmapTest is BaseTest { int24 finalTick = tick; tick = currentTick(); wordPos = type(int16).min; - for (uint256 counter; counter < 256; ++counter) { + for (uint256 counter; counter < numIters; ++counter) { (tick, wordPos, tickWord) = TickBitmap.nextInitializedTick( V3PoolCallee.wrap(pool), tick - 1, @@ -208,11 +211,12 @@ contract TickBitmapTest is BaseTest { /// @notice Test nextInitializedTick in a fork function test_NextInitializedTick_GT() public { createFork(); + uint256 numIters = dex == DEX.PancakeSwapV3 ? 10 : 256; int24 tick = currentTick(); bool initialized; int16 wordPos = type(int16).min; uint256 tickWord; - for (uint256 counter; counter < 256; ) { + for (uint256 counter; counter < numIters; ) { (tick, initialized, wordPos, tickWord) = TickBitmap.nextInitializedTickWithinOneWord( V3PoolCallee.wrap(pool), tick, @@ -230,7 +234,7 @@ contract TickBitmapTest is BaseTest { int24 finalTick = tick; tick = currentTick(); wordPos = type(int16).min; - for (uint256 counter; counter < 256; ++counter) { + for (uint256 counter; counter < numIters; ++counter) { (tick, wordPos, tickWord) = TickBitmap.nextInitializedTick( V3PoolCallee.wrap(pool), tick, @@ -243,3 +247,10 @@ contract TickBitmapTest is BaseTest { assertEq(tick, finalTick); } } + +contract TickBitmapPCSTest is TickBitmapTest { + function setUp() public override { + dex = DEX.PancakeSwapV3; + super.setUp(); + } +} diff --git a/test/TickMath.t.sol b/test/TickMath.t.sol index bed5654..5f1c279 100644 --- a/test/TickMath.t.sol +++ b/test/TickMath.t.sol @@ -16,12 +16,12 @@ contract TickMathWrapper is ITickMath { } /// @dev Test assembly optimized `TickMath` against the original. -contract TickMathTest is BaseTest { +contract TickMathTest is Test { // Wrapper that exposes the original LiquidityMath library. ITickMath internal ogWrapper; TickMathWrapper internal wrapper; - function setUp() public override { + function setUp() public { ogWrapper = ITickMath(deployCode("TickMathTest.sol")); wrapper = new TickMathWrapper(); } diff --git a/yarn.lock b/yarn.lock index a47174a..add95f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@openzeppelin/contracts-upgradeable@3.4.2-solc-0.7": + version "3.4.2-solc-0.7" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2-solc-0.7.tgz#9a5cdaf9f65bcd9a3b7e865f44a6f672afe7785e" + integrity sha512-I5iKKS8U9L1XdSxsNAIBQekN0U9hTgdleoyntIdR7Jy3U/z/NZ/1oUM0v5HnUMrmn/bXLvYL94rBvaLF++Ndnw== + "@openzeppelin/contracts@3.4.2-solc-0.7": version "3.4.2-solc-0.7" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" @@ -12,6 +17,22 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA== +"@pancakeswap/v3-core@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pancakeswap/v3-core/-/v3-core-1.0.2.tgz#f5abf6a98535f33edebbfc11ab40b4fdcee51420" + integrity sha512-9aZU8I1J6SbZOSW7NcNxuyaAC17tGkOaZJM9aJgvl6MMUOExpq0i0EC/jc3HxWbpC8sbZL+8eG544NEJs8CS+w== + +"@pancakeswap/v3-periphery@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pancakeswap/v3-periphery/-/v3-periphery-1.0.2.tgz#f5b899c60bae0fadcfff1cc2b72c802b9d5a5596" + integrity sha512-kWQhJsAG5Ij1cubKlX0JuJ3GEPFiPBR+NQt77SxHNjk62eallLyfbWJYk2NMnTO0crbjBFO5GKKQAXfp2n76vg== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@openzeppelin/contracts-upgradeable" "3.4.2-solc-0.7" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "1.0.1" + base64-sol "1.0.1" + "@solidity-parser/parser@^0.17.0": version "0.17.0" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" @@ -22,7 +43,7 @@ resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== -"@uniswap/v2-core@^1.0.1": +"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==