- We have decided to now support calling our executors normally (without using delegatecalls) since they now support specifying token transfers. - This was disabled for UniswapV3 in the past also because of data decoding issues. This seems to be the solution, though.
198 lines
5.9 KiB
Solidity
198 lines
5.9 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
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 "@interfaces/ICallback.sol";
|
|
import {TokenTransfer} from "./TokenTransfer.sol";
|
|
|
|
error UniswapV3Executor__InvalidDataLength();
|
|
error UniswapV3Executor__InvalidFactory();
|
|
error UniswapV3Executor__InvalidTarget();
|
|
error UniswapV3Executor__InvalidInitCode();
|
|
error UniswapV3Executor__InvalidTransferType(uint8 transferType);
|
|
|
|
contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer {
|
|
using SafeERC20 for IERC20;
|
|
|
|
uint160 private constant MIN_SQRT_RATIO = 4295128739;
|
|
uint160 private constant MAX_SQRT_RATIO =
|
|
1461446703485210103287273052203988822378723970342;
|
|
|
|
address public immutable factory;
|
|
bytes32 public immutable initCode;
|
|
address private immutable self;
|
|
|
|
constructor(address _factory, bytes32 _initCode, address _permit2)
|
|
TokenTransfer(_permit2)
|
|
{
|
|
if (_factory == address(0)) {
|
|
revert UniswapV3Executor__InvalidFactory();
|
|
}
|
|
if (_initCode == bytes32(0)) {
|
|
revert UniswapV3Executor__InvalidInitCode();
|
|
}
|
|
factory = _factory;
|
|
initCode = _initCode;
|
|
self = address(this);
|
|
}
|
|
|
|
// slither-disable-next-line locked-ether
|
|
function swap(uint256 amountIn, bytes calldata data)
|
|
external
|
|
payable
|
|
returns (uint256 amountOut)
|
|
{
|
|
(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint24 fee,
|
|
address receiver,
|
|
address target,
|
|
bool zeroForOne,
|
|
TransferType transferType
|
|
) = _decodeData(data);
|
|
|
|
_verifyPairAddress(tokenIn, tokenOut, fee, target);
|
|
|
|
int256 amount0;
|
|
int256 amount1;
|
|
IUniswapV3Pool pool = IUniswapV3Pool(target);
|
|
|
|
bytes memory callbackData =
|
|
_makeV3CallbackData(tokenIn, tokenOut, fee, transferType);
|
|
|
|
{
|
|
(amount0, amount1) = pool.swap(
|
|
receiver,
|
|
zeroForOne,
|
|
// positive means exactIn
|
|
int256(amountIn),
|
|
zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,
|
|
callbackData
|
|
);
|
|
}
|
|
|
|
if (zeroForOne) {
|
|
amountOut = amount1 > 0 ? uint256(amount1) : uint256(-amount1);
|
|
} else {
|
|
amountOut = amount0 > 0 ? uint256(amount0) : uint256(-amount0);
|
|
}
|
|
}
|
|
|
|
function handleCallback(bytes calldata msgData)
|
|
public
|
|
returns (bytes memory result)
|
|
{
|
|
// The data has the following layout:
|
|
// - selector (4 bytes)
|
|
// - amount0Delta (32 bytes)
|
|
// - amount1Delta (32 bytes)
|
|
// - dataOffset (32 bytes)
|
|
// - dataLength (32 bytes)
|
|
// - protocolData (variable length)
|
|
|
|
(int256 amount0Delta, int256 amount1Delta) =
|
|
abi.decode(msgData[4:68], (int256, int256));
|
|
|
|
address tokenIn = address(bytes20(msgData[132:152]));
|
|
|
|
// Transfer type does not exist
|
|
if (uint8(msgData[175]) > uint8(TransferType.NONE)) {
|
|
revert UniswapV3Executor__InvalidTransferType(uint8(msgData[175]));
|
|
}
|
|
|
|
TransferType transferType = TransferType(uint8(msgData[175]));
|
|
address sender = address(bytes20(msgData[176:196]));
|
|
|
|
verifyCallback(msgData[132:]);
|
|
|
|
uint256 amountOwed =
|
|
amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta);
|
|
|
|
_transfer(tokenIn, sender, msg.sender, amountOwed, transferType);
|
|
|
|
return abi.encode(amountOwed, tokenIn);
|
|
}
|
|
|
|
function verifyCallback(bytes calldata data) public view {
|
|
address tokenIn = address(bytes20(data[0:20]));
|
|
address tokenOut = address(bytes20(data[20:40]));
|
|
uint24 poolFee = uint24(bytes3(data[40:43]));
|
|
|
|
_verifyPairAddress(tokenIn, tokenOut, poolFee, msg.sender);
|
|
}
|
|
|
|
function uniswapV3SwapCallback(
|
|
int256, /* amount0Delta */
|
|
int256, /* amount1Delta */
|
|
bytes calldata /* data */
|
|
) external {
|
|
handleCallback(msg.data);
|
|
}
|
|
|
|
function _decodeData(bytes calldata data)
|
|
internal
|
|
pure
|
|
returns (
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint24 fee,
|
|
address receiver,
|
|
address target,
|
|
bool zeroForOne,
|
|
TransferType transferType
|
|
)
|
|
{
|
|
if (data.length != 85) {
|
|
revert UniswapV3Executor__InvalidDataLength();
|
|
}
|
|
tokenIn = address(bytes20(data[0:20]));
|
|
tokenOut = address(bytes20(data[20:40]));
|
|
fee = uint24(bytes3(data[40:43]));
|
|
receiver = address(bytes20(data[43:63]));
|
|
target = address(bytes20(data[63:83]));
|
|
zeroForOne = uint8(data[83]) > 0;
|
|
transferType = TransferType(uint8(data[84]));
|
|
}
|
|
|
|
function _makeV3CallbackData(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint24 fee,
|
|
TransferType transferType
|
|
) internal view returns (bytes memory) {
|
|
return abi.encodePacked(
|
|
tokenIn, tokenOut, fee, uint8(transferType), msg.sender
|
|
);
|
|
}
|
|
|
|
function _verifyPairAddress(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint24 fee,
|
|
address target
|
|
) internal view {
|
|
(address token0, address token1) =
|
|
tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
|
|
address pool = address(
|
|
uint160(
|
|
uint256(
|
|
keccak256(
|
|
abi.encodePacked(
|
|
hex"ff",
|
|
factory,
|
|
keccak256(abi.encode(token0, token1, fee)),
|
|
initCode
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
if (pool != target) {
|
|
revert UniswapV3Executor__InvalidTarget();
|
|
}
|
|
}
|
|
}
|