feat: Pancakeswap V3 support
This commit is contained in:
@@ -18,12 +18,14 @@ library CallbackValidationV2 {
|
||||
address factory,
|
||||
address tokenA,
|
||||
address tokenB,
|
||||
uint24 fee
|
||||
uint24 fee,
|
||||
bytes32 initCode
|
||||
) internal view returns (IUniswapV3Pool pool) {
|
||||
return
|
||||
verifyCallback(
|
||||
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
|
||||
function verifyCallback(
|
||||
address factory,
|
||||
PoolAddressV2.PoolKey memory poolKey
|
||||
PoolAddressV2.PoolKey memory poolKey,
|
||||
bytes32 initCode
|
||||
) internal view returns (IUniswapV3Pool pool) {
|
||||
pool = IUniswapV3Pool(PoolAddressV2.computeAddress(factory, poolKey));
|
||||
pool = IUniswapV3Pool(PoolAddressV2.computeAddress(factory, poolKey, initCode));
|
||||
require(msg.sender == address(pool), "CV");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ pragma solidity >=0.5.0;
|
||||
|
||||
/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
|
||||
library PoolAddressV2 {
|
||||
bytes32 internal constant POOL_INIT_CODE_HASH =
|
||||
0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
|
||||
|
||||
/// @notice The identifying key of the pool
|
||||
struct PoolKey {
|
||||
@@ -33,7 +31,7 @@ library PoolAddressV2 {
|
||||
/// @param factory The Uniswap V3 factory contract address
|
||||
/// @param key The PoolKey
|
||||
/// @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
|
||||
pure
|
||||
returns (address pool)
|
||||
@@ -49,7 +47,7 @@ library PoolAddressV2 {
|
||||
keccak256(
|
||||
abi.encode(key.token0, key.token1, key.fee)
|
||||
),
|
||||
POOL_INIT_CODE_HASH
|
||||
initCode
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -25,6 +25,11 @@ const executors_to_deploy = {
|
||||
"0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
||||
"0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"
|
||||
]},
|
||||
// PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash
|
||||
{exchange: "UniswapV3Executor", args: [
|
||||
"0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9",
|
||||
"0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2"
|
||||
]},
|
||||
// Args: Pool manager
|
||||
{exchange: "UniswapV4Executor", args: ["0x000000000004444c5dc75cB358380D2e3dE08A90"]},
|
||||
{exchange: "BalancerV2Executor", args: []},
|
||||
@@ -50,11 +55,15 @@ const executors_to_deploy = {
|
||||
"0x33128a8fC17869897dcE68Ed026d694621f6FDfD",
|
||||
"0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"
|
||||
]},
|
||||
// PANCAKESWAP V3 - Args: Deployer, Pool Init Code Hash
|
||||
{exchange: "UniswapV3Executor", args: [
|
||||
"0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9",
|
||||
"0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2"
|
||||
]},
|
||||
// Args: Pool manager
|
||||
{exchange: "UniswapV4Executor", args: ["0x498581ff718922c3f8e6a244956af099b2652b2b"]},
|
||||
{exchange: "BalancerV2Executor", args: []},
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -518,6 +518,25 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
|
||||
_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.
|
||||
*/
|
||||
|
||||
@@ -107,7 +107,9 @@ contract UniswapV3Executor is IExecutor, ICallback {
|
||||
uint24 poolFee = uint24(bytes3(data[40:43]));
|
||||
|
||||
// slither-disable-next-line unused-return
|
||||
CallbackValidationV2.verifyCallback(factory, tokenIn, tokenOut, poolFee);
|
||||
CallbackValidationV2.verifyCallback(
|
||||
factory, tokenIn, tokenOut, poolFee, initCode
|
||||
);
|
||||
}
|
||||
|
||||
function uniswapV3SwapCallback(
|
||||
|
||||
@@ -753,6 +753,39 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
||||
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 {
|
||||
// Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2
|
||||
// 1 WETH -> DAI
|
||||
|
||||
@@ -36,6 +36,7 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
address tychoRouterAddr;
|
||||
UniswapV2Executor public usv2Executor;
|
||||
UniswapV3Executor public usv3Executor;
|
||||
UniswapV3Executor public pancakev3Executor;
|
||||
UniswapV4Executor public usv4Executor;
|
||||
MockERC20[] tokens;
|
||||
|
||||
@@ -46,8 +47,10 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
vm.startPrank(ADMIN);
|
||||
address factoryV2 = USV2_FACTORY_ETHEREUM;
|
||||
address factoryV3 = USV3_FACTORY_ETHEREUM;
|
||||
address factoryPancakeV3 = PANCAKESWAPV3_DEPLOYER_ETHEREUM;
|
||||
bytes32 initCodeV2 = USV2_POOL_CODE_INIT_HASH;
|
||||
bytes32 initCodeV3 = USV3_POOL_CODE_INIT_HASH;
|
||||
bytes32 initCodePancakeV3 = PANCAKEV3_POOL_CODE_INIT_HASH;
|
||||
address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
||||
IPoolManager poolManager = IPoolManager(poolManagerAddress);
|
||||
tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR);
|
||||
@@ -65,11 +68,14 @@ contract TychoRouterTestSetup is Test, Constants {
|
||||
usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2);
|
||||
usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3);
|
||||
usv4Executor = new UniswapV4Executor(poolManager);
|
||||
pancakev3Executor =
|
||||
new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3);
|
||||
vm.startPrank(EXECUTOR_SETTER);
|
||||
address[] memory executors = new address[](3);
|
||||
address[] memory executors = new address[](4);
|
||||
executors[0] = address(usv2Executor);
|
||||
executors[1] = address(usv3Executor);
|
||||
executors[2] = address(usv4Executor);
|
||||
executors[2] = address(pancakev3Executor);
|
||||
executors[3] = address(usv4Executor);
|
||||
tychoRouter.setExecutors(executors);
|
||||
vm.stopPrank();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user