- For protocols like Balancer and Curve, which expect funds to be in the router at the time of swap, we must support also transferring funds from the user into the router. Doing this in the router would mean we are dealing with transfers in two different places: in the router main methods and in the executors. To avoid this, we are now performing transfers just in the executors, and two transfer types have been added to support transfers into the router. TODO: - Add this for Balancer and Curve (only added for USV4 atm). - TODO consider renaming TRANSFER_FROM and TRANSFER_PERMIT2 to include "pool" in the name
127 lines
3.9 KiB
Solidity
127 lines
3.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-v2/contracts/interfaces/IUniswapV2Pair.sol";
|
|
import "./TokenTransfer.sol";
|
|
|
|
error UniswapV2Executor__InvalidDataLength();
|
|
error UniswapV2Executor__InvalidTarget();
|
|
error UniswapV2Executor__InvalidFactory();
|
|
error UniswapV2Executor__InvalidInitCode();
|
|
|
|
contract UniswapV2Executor is IExecutor, TokenTransfer {
|
|
using SafeERC20 for IERC20;
|
|
|
|
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 UniswapV2Executor__InvalidFactory();
|
|
}
|
|
if (_initCode == bytes32(0)) {
|
|
revert UniswapV2Executor__InvalidInitCode();
|
|
}
|
|
factory = _factory;
|
|
initCode = _initCode;
|
|
self = address(this);
|
|
}
|
|
|
|
// slither-disable-next-line locked-ether
|
|
function swap(uint256 givenAmount, bytes calldata data)
|
|
external
|
|
payable
|
|
returns (uint256 calculatedAmount)
|
|
{
|
|
IERC20 tokenIn;
|
|
address target;
|
|
address receiver;
|
|
bool zeroForOne;
|
|
TransferType transferType;
|
|
|
|
(tokenIn, target, receiver, zeroForOne, transferType) =
|
|
_decodeData(data);
|
|
|
|
_verifyPairAddress(target);
|
|
|
|
calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne);
|
|
_transfer(
|
|
address(tokenIn), msg.sender, target, givenAmount, transferType
|
|
);
|
|
|
|
IUniswapV2Pair pool = IUniswapV2Pair(target);
|
|
if (zeroForOne) {
|
|
pool.swap(0, calculatedAmount, receiver, "");
|
|
} else {
|
|
pool.swap(calculatedAmount, 0, receiver, "");
|
|
}
|
|
}
|
|
|
|
function _decodeData(bytes calldata data)
|
|
internal
|
|
pure
|
|
returns (
|
|
IERC20 inToken,
|
|
address target,
|
|
address receiver,
|
|
bool zeroForOne,
|
|
TransferType transferType
|
|
)
|
|
{
|
|
if (data.length != 62) {
|
|
revert UniswapV2Executor__InvalidDataLength();
|
|
}
|
|
inToken = IERC20(address(bytes20(data[0:20])));
|
|
target = address(bytes20(data[20:40]));
|
|
receiver = address(bytes20(data[40:60]));
|
|
zeroForOne = uint8(data[60]) > 0;
|
|
transferType = TransferType(uint8(data[61]));
|
|
}
|
|
|
|
function _getAmountOut(address target, uint256 amountIn, bool zeroForOne)
|
|
internal
|
|
view
|
|
returns (uint256 amount)
|
|
{
|
|
IUniswapV2Pair pair = IUniswapV2Pair(target);
|
|
uint112 reserveIn;
|
|
uint112 reserveOut;
|
|
if (zeroForOne) {
|
|
// slither-disable-next-line unused-return
|
|
(reserveIn, reserveOut,) = pair.getReserves();
|
|
} else {
|
|
// slither-disable-next-line unused-return
|
|
(reserveOut, reserveIn,) = pair.getReserves();
|
|
}
|
|
|
|
require(reserveIn > 0 && reserveOut > 0, "L");
|
|
uint256 amountInWithFee = amountIn * 997;
|
|
uint256 numerator = amountInWithFee * uint256(reserveOut);
|
|
uint256 denominator = (uint256(reserveIn) * 1000) + amountInWithFee;
|
|
amount = numerator / denominator;
|
|
}
|
|
|
|
function _verifyPairAddress(address target) internal view {
|
|
address token0 = IUniswapV2Pair(target).token0();
|
|
address token1 = IUniswapV2Pair(target).token1();
|
|
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
|
|
address pair = address(
|
|
uint160(
|
|
uint256(
|
|
keccak256(
|
|
abi.encodePacked(hex"ff", factory, salt, initCode)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
if (pair != target) {
|
|
revert UniswapV2Executor__InvalidTarget();
|
|
}
|
|
}
|
|
}
|