Merge pull request #127 from propeller-heads/router/tnl/ENG-4409-pancake-v3-callback
feat: Pancakeswap V3 support
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"sushiswap_v2": "0x8ABd4075cF54E0A9C52D18e6951b969AF3249cF9",
|
||||
"pancakeswap_v2": "0x9fC9e63cCf5F773A8bC79DcfA38c581B0DEa1d11",
|
||||
"uniswap_v3": "0xdD8559c917393FC8DD2b4dD289c52Ff445fDE1B0",
|
||||
"pancakeswap_v3": "0x4929B619A8F0D9c06ed0FfD497636580D823F65d",
|
||||
"uniswap_v4": "0x042C0ebBEAb9d9987c2f64Ee05f2B3aeB86eAf70",
|
||||
"vm:balancer_v2": "0x00BE8EfAE40219Ff76287b0F9b9e497942f5BC91"
|
||||
},
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// Updated v3 lib to solidity >=0.7.6
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
pragma solidity >=0.7.6;
|
||||
|
||||
import "./PoolAddressV2.sol";
|
||||
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
|
||||
/// @notice Provides validation for callbacks from Uniswap V3 Pools
|
||||
library CallbackValidationV2 {
|
||||
/// @notice Returns the address of a valid Uniswap V3 Pool
|
||||
/// @param factory The contract address of the Uniswap V3 factory
|
||||
/// @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 factory,
|
||||
address tokenA,
|
||||
address tokenB,
|
||||
uint24 fee
|
||||
) internal view returns (IUniswapV3Pool pool) {
|
||||
return
|
||||
verifyCallback(
|
||||
factory,
|
||||
PoolAddressV2.getPoolKey(tokenA, tokenB, fee)
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Returns the address of a valid Uniswap V3 Pool
|
||||
/// @param factory The contract address of the Uniswap V3 factory
|
||||
/// @param poolKey The identifying key of the V3 pool
|
||||
/// @return pool The V3 pool contract address
|
||||
function verifyCallback(
|
||||
address factory,
|
||||
PoolAddressV2.PoolKey memory poolKey
|
||||
) internal view returns (IUniswapV3Pool pool) {
|
||||
pool = IUniswapV3Pool(PoolAddressV2.computeAddress(factory, poolKey));
|
||||
require(msg.sender == address(pool), "CV");
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Updated v3 lib to solidity >=0.7.6
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
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 {
|
||||
address token0;
|
||||
address token1;
|
||||
uint24 fee;
|
||||
}
|
||||
|
||||
/// @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 Poolkey The pool details with ordered token0 and token1 assignments
|
||||
function getPoolKey(address tokenA, address tokenB, uint24 fee)
|
||||
internal
|
||||
pure
|
||||
returns (PoolKey memory)
|
||||
{
|
||||
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
|
||||
return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
|
||||
}
|
||||
|
||||
/// @notice Deterministically computes the pool address given the factory and PoolKey
|
||||
/// @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)
|
||||
internal
|
||||
pure
|
||||
returns (address pool)
|
||||
{
|
||||
require(key.token0 < key.token1);
|
||||
pool = address(
|
||||
uint160(
|
||||
uint256(
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
hex"ff",
|
||||
factory,
|
||||
keccak256(
|
||||
abi.encode(key.token0, key.token1, key.fee)
|
||||
),
|
||||
POOL_INIT_CODE_HASH
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -47,4 +47,4 @@ For each of the following, you must select one of `tenderly_ethereum`, `tenderly
|
||||
|
||||
1. In `scripts/deploy-executors.js` define the executors to be deployed
|
||||
2. Deploy executors: `npx hardhat run scripts/deploy-executors.js --network NETWORK`
|
||||
3. Fill in the executor addresses in `config/executors.json`
|
||||
3. Fill in the executor addresses in `config/executor_addresses.json`
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -51,7 +51,7 @@ async function main() {
|
||||
// Set executors
|
||||
const executorAddresses = executorsToSet.map(executor => executor.executor);
|
||||
const tx = await router.setExecutors(executorAddresses, {
|
||||
gasLimit: 200000 // should be around 50k per executor
|
||||
gasLimit: 300000 // should be around 50k per executor
|
||||
});
|
||||
await tx.wait(); // Wait for the transaction to be mined
|
||||
console.log(`Executors set at transaction: ${tx.hash}`);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,6 @@ pragma solidity ^0.8.26;
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import "@uniswap/v3-updated/CallbackValidationV2.sol";
|
||||
import "@interfaces/ICallback.sol";
|
||||
|
||||
error UniswapV3Executor__InvalidDataLength();
|
||||
@@ -106,8 +105,7 @@ contract UniswapV3Executor is IExecutor, ICallback {
|
||||
address tokenOut = address(bytes20(data[20:40]));
|
||||
uint24 poolFee = uint24(bytes3(data[40:43]));
|
||||
|
||||
// slither-disable-next-line unused-return
|
||||
CallbackValidationV2.verifyCallback(factory, tokenIn, tokenOut, poolFee);
|
||||
_verifyPairAddress(tokenIn, tokenOut, poolFee, msg.sender);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ impl SwapEncoderBuilder {
|
||||
"pancakeswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address))),
|
||||
"vm:balancer_v2" => Ok(Box::new(BalancerV2SwapEncoder::new(self.executor_address))),
|
||||
"uniswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))),
|
||||
"pancakeswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))),
|
||||
"uniswap_v4" => Ok(Box::new(UniswapV4SwapEncoder::new(self.executor_address))),
|
||||
_ => Err(EncodingError::FatalError(format!(
|
||||
"Unknown protocol system: {}",
|
||||
|
||||
Reference in New Issue
Block a user