- In Tycho Router - Fix integration tests - If the trade is cyclical, we can't do the balance check for the correctness of the amountOut transfers - In encoding, - if it is a wrap trade, the transfer should be just a normal Transfer (not TransferFrom nor a Permit2Transfer) - add test names to println! statements --- don't change below this line --- ENG-4315 Took 1 hour 0 minutes Took 30 seconds Took 21 seconds Took 16 seconds Took 17 seconds
166 lines
4.9 KiB
Solidity
166 lines
4.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 "./TokenTransfer.sol";
|
|
import "@openzeppelin/contracts/utils/Address.sol";
|
|
|
|
error CurveExecutor__AddressZero();
|
|
error CurveExecutor__InvalidDataLength();
|
|
|
|
interface CryptoPool {
|
|
// slither-disable-next-line naming-convention
|
|
function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy)
|
|
external
|
|
payable;
|
|
}
|
|
|
|
interface StablePool {
|
|
// slither-disable-next-line naming-convention
|
|
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
|
external
|
|
payable;
|
|
}
|
|
|
|
interface CryptoPoolETH {
|
|
// slither-disable-start naming-convention
|
|
function exchange(
|
|
uint256 i,
|
|
uint256 j,
|
|
uint256 dx,
|
|
uint256 min_dy,
|
|
bool use_eth
|
|
) external payable;
|
|
// slither-disable-end naming-convention
|
|
}
|
|
|
|
contract CurveExecutor is IExecutor, TokenTransfer {
|
|
using SafeERC20 for IERC20;
|
|
|
|
address public immutable nativeToken;
|
|
|
|
constructor(address _nativeToken, address _permit2)
|
|
TokenTransfer(_permit2)
|
|
{
|
|
if (_nativeToken == address(0)) {
|
|
revert CurveExecutor__AddressZero();
|
|
}
|
|
nativeToken = _nativeToken;
|
|
}
|
|
|
|
// slither-disable-next-line locked-ether
|
|
function swap(uint256 amountIn, bytes calldata data)
|
|
external
|
|
payable
|
|
returns (uint256)
|
|
{
|
|
if (data.length != 85) revert CurveExecutor__InvalidDataLength();
|
|
|
|
(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
address pool,
|
|
uint8 poolType,
|
|
int128 i,
|
|
int128 j,
|
|
bool tokenApprovalNeeded,
|
|
TransferType transferType,
|
|
address receiver
|
|
) = _decodeData(data);
|
|
|
|
_transfer(
|
|
tokenIn,
|
|
msg.sender,
|
|
// Receiver can never be the pool, since the pool expects funds in the router contract
|
|
// Thus, this call will only ever be used to transfer funds from the user into the router.
|
|
address(this),
|
|
amountIn,
|
|
transferType
|
|
);
|
|
|
|
if (tokenApprovalNeeded && tokenIn != nativeToken) {
|
|
// slither-disable-next-line unused-return
|
|
IERC20(tokenIn).approve(address(pool), type(uint256).max);
|
|
}
|
|
|
|
/// Inspired by Curve's router contract: https://github.com/curvefi/curve-router-ng/blob/9ab006ca848fc7f1995b6fbbecfecc1e0eb29e2a/contracts/Router.vy#L44
|
|
uint256 balanceBefore = _balanceOf(tokenOut);
|
|
|
|
uint256 ethAmount = 0;
|
|
if (tokenIn == nativeToken) {
|
|
ethAmount = amountIn;
|
|
}
|
|
|
|
if (poolType == 1 || poolType == 10) {
|
|
// stable and stable_ng
|
|
// slither-disable-next-line arbitrary-send-eth
|
|
StablePool(pool).exchange{value: ethAmount}(i, j, amountIn, 0);
|
|
} else {
|
|
// crypto or llamma
|
|
if (tokenIn == nativeToken || tokenOut == nativeToken) {
|
|
// slither-disable-next-line arbitrary-send-eth
|
|
CryptoPoolETH(pool).exchange{value: ethAmount}(
|
|
uint256(int256(i)), uint256(int256(j)), amountIn, 0, true
|
|
);
|
|
} else {
|
|
CryptoPool(pool).exchange(
|
|
uint256(int256(i)), uint256(int256(j)), amountIn, 0
|
|
);
|
|
}
|
|
}
|
|
|
|
uint256 balanceAfter = _balanceOf(tokenOut);
|
|
uint256 amountOut = balanceAfter - balanceBefore;
|
|
|
|
if (receiver != address(this)) {
|
|
if (tokenOut == nativeToken) {
|
|
Address.sendValue(payable(receiver), amountOut);
|
|
} else {
|
|
IERC20(tokenOut).safeTransfer(receiver, amountOut);
|
|
}
|
|
}
|
|
return amountOut;
|
|
}
|
|
|
|
function _decodeData(bytes calldata data)
|
|
internal
|
|
pure
|
|
returns (
|
|
address tokenIn,
|
|
address tokenOut,
|
|
address pool,
|
|
uint8 poolType,
|
|
int128 i,
|
|
int128 j,
|
|
bool tokenApprovalNeeded,
|
|
TransferType transferType,
|
|
address receiver
|
|
)
|
|
{
|
|
tokenIn = address(bytes20(data[0:20]));
|
|
tokenOut = address(bytes20(data[20:40]));
|
|
pool = address(bytes20(data[40:60]));
|
|
poolType = uint8(data[60]);
|
|
i = int128(uint128(uint8(data[61])));
|
|
j = int128(uint128(uint8(data[62])));
|
|
tokenApprovalNeeded = data[63] != 0;
|
|
transferType = TransferType(uint8(data[64]));
|
|
receiver = address(bytes20(data[65:85]));
|
|
}
|
|
|
|
receive() external payable {
|
|
require(msg.sender.code.length != 0);
|
|
}
|
|
|
|
function _balanceOf(address token)
|
|
internal
|
|
view
|
|
returns (uint256 balance)
|
|
{
|
|
balance = token == nativeToken
|
|
? address(this).balance
|
|
: IERC20(token).balanceOf(address(this));
|
|
}
|
|
}
|