feat: Pancakeswap V3 support

This commit is contained in:
TAMARA LIPOWSKI
2025-03-21 11:26:41 -04:00
parent 59a5a558f5
commit d582543057
7 changed files with 82 additions and 12 deletions

View File

@@ -18,12 +18,14 @@ library CallbackValidationV2 {
address factory, address factory,
address tokenA, address tokenA,
address tokenB, address tokenB,
uint24 fee uint24 fee,
bytes32 initCode
) internal view returns (IUniswapV3Pool pool) { ) internal view returns (IUniswapV3Pool pool) {
return return
verifyCallback( verifyCallback(
factory, factory,
PoolAddressV2.getPoolKey(tokenA, tokenB, fee) PoolAddressV2.getPoolKey(tokenA, tokenB, fee),
initCode
); );
} }
@@ -33,9 +35,10 @@ library CallbackValidationV2 {
/// @return pool The V3 pool contract address /// @return pool The V3 pool contract address
function verifyCallback( function verifyCallback(
address factory, address factory,
PoolAddressV2.PoolKey memory poolKey PoolAddressV2.PoolKey memory poolKey,
bytes32 initCode
) internal view returns (IUniswapV3Pool pool) { ) internal view returns (IUniswapV3Pool pool) {
pool = IUniswapV3Pool(PoolAddressV2.computeAddress(factory, poolKey)); pool = IUniswapV3Pool(PoolAddressV2.computeAddress(factory, poolKey, initCode));
require(msg.sender == address(pool), "CV"); require(msg.sender == address(pool), "CV");
} }
} }

View File

@@ -5,8 +5,6 @@ pragma solidity >=0.5.0;
/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddressV2 { library PoolAddressV2 {
bytes32 internal constant POOL_INIT_CODE_HASH =
0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
/// @notice The identifying key of the pool /// @notice The identifying key of the pool
struct PoolKey { struct PoolKey {
@@ -33,7 +31,7 @@ library PoolAddressV2 {
/// @param factory The Uniswap V3 factory contract address /// @param factory The Uniswap V3 factory contract address
/// @param key The PoolKey /// @param key The PoolKey
/// @return pool The contract address of the V3 pool /// @return pool The contract address of the V3 pool
function computeAddress(address factory, PoolKey memory key) function computeAddress(address factory, PoolKey memory key, bytes32 initCode)
internal internal
pure pure
returns (address pool) returns (address pool)
@@ -49,7 +47,7 @@ library PoolAddressV2 {
keccak256( keccak256(
abi.encode(key.token0, key.token1, key.fee) abi.encode(key.token0, key.token1, key.fee)
), ),
POOL_INIT_CODE_HASH initCode
) )
) )
) )

View File

@@ -25,6 +25,11 @@ const executors_to_deploy = {
"0x1F98431c8aD98523631AE4a59f267346ea31F984", "0x1F98431c8aD98523631AE4a59f267346ea31F984",
"0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"
]}, ]},
// PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash
{exchange: "UniswapV3Executor", args: [
"0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9",
"0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2"
]},
// Args: Pool manager // Args: Pool manager
{exchange: "UniswapV4Executor", args: ["0x000000000004444c5dc75cB358380D2e3dE08A90"]}, {exchange: "UniswapV4Executor", args: ["0x000000000004444c5dc75cB358380D2e3dE08A90"]},
{exchange: "BalancerV2Executor", args: []}, {exchange: "BalancerV2Executor", args: []},
@@ -50,11 +55,15 @@ const executors_to_deploy = {
"0x33128a8fC17869897dcE68Ed026d694621f6FDfD", "0x33128a8fC17869897dcE68Ed026d694621f6FDfD",
"0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"
]}, ]},
// PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash
{exchange: "UniswapV3Executor", args: [
"0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9",
"0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2"
]},
// Args: Pool manager // Args: Pool manager
{exchange: "UniswapV4Executor", args: ["0x498581ff718922c3f8e6a244956af099b2652b2b"]}, {exchange: "UniswapV4Executor", args: ["0x498581ff718922c3f8e6a244956af099b2652b2b"]},
{exchange: "BalancerV2Executor", args: []}, {exchange: "BalancerV2Executor", args: []},
], ],
} }
async function main() { async function main() {

View File

@@ -518,6 +518,25 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
_handleCallback(fullData); _handleCallback(fullData);
} }
/**
* @dev Called by PancakeV3 pool when swapping on it.
*/
function pancakeV3SwapCallback(
int256, /* amount0Delta */
int256, /* amount1Delta */
bytes calldata data
) external {
if (data.length < 24) revert TychoRouter__InvalidDataLength();
// We are taking advantage of the fact that the data we need is already encoded in the correct format inside msg.data
// This way we preserve the bytes calldata (and don't need to convert it to bytes memory)
uint256 dataOffset = 4 + 32 + 32 + 32; // Skip selector + 2 ints + data_offset
uint256 dataLength =
uint256(bytes32(msg.data[dataOffset:dataOffset + 32]));
bytes calldata fullData = msg.data[4:dataOffset + 32 + dataLength];
_handleCallback(fullData);
}
/** /**
* @dev Called by UniswapV4 pool manager after achieving unlock state. * @dev Called by UniswapV4 pool manager after achieving unlock state.
*/ */

View File

@@ -107,7 +107,9 @@ contract UniswapV3Executor is IExecutor, ICallback {
uint24 poolFee = uint24(bytes3(data[40:43])); uint24 poolFee = uint24(bytes3(data[40:43]));
// slither-disable-next-line unused-return // slither-disable-next-line unused-return
CallbackValidationV2.verifyCallback(factory, tokenIn, tokenOut, poolFee); CallbackValidationV2.verifyCallback(
factory, tokenIn, tokenOut, poolFee, initCode
);
} }
function uniswapV3SwapCallback( function uniswapV3SwapCallback(

View File

@@ -753,6 +753,39 @@ contract TychoRouterTest is TychoRouterTestSetup {
assertGe(finalBalance, expAmountOut); assertGe(finalBalance, expAmountOut);
} }
function testSwapSinglePancakeV3() public {
// Trade 1 WETH for USDT with 1 swap on Pancakeswap V3
// 1 WETH -> USDT
// (PancakeV3)
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, tychoRouterAddr, amountIn);
uint256 expAmountOut = 2659_567519; //Swap 1 WETH for 1205.12 DAI
bool zeroForOne = true;
bytes memory protocolData = encodeUniswapV3Swap(
WETH_ADDR,
USDT_ADDR,
tychoRouterAddr,
PANCAKESWAPV3_WETH_USDT_POOL,
zeroForOne
);
bytes memory swap = encodeSwap(
uint8(0),
uint8(1),
uint24(0),
address(pancakev3Executor),
protocolData
);
bytes[] memory swaps = new bytes[](1);
swaps[0] = swap;
tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps));
uint256 finalBalance = IERC20(USDT_ADDR).balanceOf(tychoRouterAddr);
assertGe(finalBalance, expAmountOut);
}
function testSwapSingleUSV3Permit2() public { function testSwapSingleUSV3Permit2() public {
// Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2 // Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2
// 1 WETH -> DAI // 1 WETH -> DAI

View File

@@ -36,6 +36,7 @@ contract TychoRouterTestSetup is Test, Constants {
address tychoRouterAddr; address tychoRouterAddr;
UniswapV2Executor public usv2Executor; UniswapV2Executor public usv2Executor;
UniswapV3Executor public usv3Executor; UniswapV3Executor public usv3Executor;
UniswapV3Executor public pancakev3Executor;
UniswapV4Executor public usv4Executor; UniswapV4Executor public usv4Executor;
MockERC20[] tokens; MockERC20[] tokens;
@@ -46,8 +47,10 @@ contract TychoRouterTestSetup is Test, Constants {
vm.startPrank(ADMIN); vm.startPrank(ADMIN);
address factoryV2 = USV2_FACTORY_ETHEREUM; address factoryV2 = USV2_FACTORY_ETHEREUM;
address factoryV3 = USV3_FACTORY_ETHEREUM; address factoryV3 = USV3_FACTORY_ETHEREUM;
address factoryPancakeV3 = PANCAKESWAPV3_DEPLOYER_ETHEREUM;
bytes32 initCodeV2 = USV2_POOL_CODE_INIT_HASH; bytes32 initCodeV2 = USV2_POOL_CODE_INIT_HASH;
bytes32 initCodeV3 = USV3_POOL_CODE_INIT_HASH; bytes32 initCodeV3 = USV3_POOL_CODE_INIT_HASH;
bytes32 initCodePancakeV3 = PANCAKEV3_POOL_CODE_INIT_HASH;
address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90; address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90;
IPoolManager poolManager = IPoolManager(poolManagerAddress); IPoolManager poolManager = IPoolManager(poolManagerAddress);
tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR); tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR);
@@ -65,11 +68,14 @@ contract TychoRouterTestSetup is Test, Constants {
usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2); usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2);
usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3); usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3);
usv4Executor = new UniswapV4Executor(poolManager); usv4Executor = new UniswapV4Executor(poolManager);
pancakev3Executor =
new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3);
vm.startPrank(EXECUTOR_SETTER); vm.startPrank(EXECUTOR_SETTER);
address[] memory executors = new address[](3); address[] memory executors = new address[](4);
executors[0] = address(usv2Executor); executors[0] = address(usv2Executor);
executors[1] = address(usv3Executor); executors[1] = address(usv3Executor);
executors[2] = address(usv4Executor); executors[2] = address(pancakev3Executor);
executors[3] = address(usv4Executor);
tychoRouter.setExecutors(executors); tychoRouter.setExecutors(executors);
vm.stopPrank(); vm.stopPrank();