// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.13 <0.9.0;
/**
Optimal MEV router contract (IUniswapV2Router compatible)
For the User it aims to offer:
1. Better order routing for minimal slippage
2. At source MEV
3. Lower gas costs for swaps and liquidity changes
For the Liquidity providers:
1. Inclusive rewards
2. Reduced impermanent loss
For the Exchange providers:
1. Inclusive rewards
2. Increased adoption
For the Ethereum environment:
1. Reduced MEV attacks and fee spikes
2. Healthy growth in MEV space with inclusive incentives
Version 1 MEV Strategies
- cross-dex backruns for swaps
- reduced slippage fallback router
The contract leverages and depends on 3 external protocols:
1. Uniswap V2 (or equivalent on another network) for backrun completion and fallback swaps
2. BentoBox for flashloan backruns
3. Aave V2 for flashloan backruns that require more liquidity than BentoBox has
Business logic
- Profits from backruns are retained on the contract to improve efficiency (gas cost and profit) of future backruns
- Contract should therefore be deployed or ownership transfered to a multisig address
- Harvest function can be called from the multisig owners to distribute profits by consensus
Resources
- IUniswapV2Router: https://github.com/Uniswap/v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol
- Contract size reduction techniques: https://ethereum.org/en/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/
- Contract gas reduction techniques: https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc
Dev Notes
- Normal sushiswap router functions. Swaps have 2 material changes:
1) slippage fallback router (uniswap v2)
2) backruns after user swap
- For gas and size efficiency, requires are modified to reverts with custom errors
- Factory hash is now passed to library functions because we are working with 2 factories
- Other changes are trade-offs for reducing contract size and / or gas usage and stack too deep errors
*/ |
|
============ Internal Imports ============ |
|
@title OpenMevRouter |
contract OpenMevRouter is IFlashBorrower, IOpenMevRouter, TwoStepOwnable {
using SafeTransferLib for ERC20;
// Custom errors save gas, encoding to 4 bytes
error Expired();
error NoTokens();
error NotPercent();
error NoReceivers();
error InvalidPath();
error TransferFailed();
error InsufficientBAmount();
error InsufficientAAmount();
error TokenIsFeeOnTransfer();
error ExcessiveInputAmount();
error ExecuteNotAuthorized();
error InsufficientAllowance();
error InsufficientLiquidity();
error InsufficientOutputAmount();
event MEV(address indexed user, address indexed token, uint256 value);
event LoanError(address indexed token, uint256 amountIn);
bytes4 internal constant SWAP_SELECTOR = bytes4(keccak256("swap(uint256,uint256,address,bytes)"));
address internal constant WETH09 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address internal constant BACKUP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // uniswap v2 factory
address internal constant LENDING_POOL_ADDRESS = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9; // aave lending pool address
address internal constant BENTO = 0xF5BCE5077908a1b7370B9ae04AdC565EBd643966;
address internal constant AAVE_DATA_PROVIDER = 0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d;
IBentoBoxV1 internal constant bento = IBentoBoxV1(0xF5BCE5077908a1b7370B9ae04AdC565EBd643966); // BENTO vault contract
mapping(address => bool) internal IS_AAVE_ASSET; // boolean address mapping for flagging aave assets
/**
* |
@notice Ensures deadline is not passed, otherwise revert. (0 = no deadline) |
function ensure(uint256 deadline) internal view {
if (deadline < block.timestamp) revert Expired();
} |
@notice Checks amounts for token A and token B are balanced for pool. Creates a pair if none exists |
function _addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin
) internal virtual returns (uint256 amountA, uint256 amountB) {
// create the pair if it doesn't exist yet
address factory = OpenMevLibrary.SUSHI_FACTORY;
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint256 reserveA, uint256 reserveB) = OpenMevLibrary.getReserves(factory, tokenA, tokenB);
if (_isZero(reserveA) && _isZero(reserveB)) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint256 amountBOptimal = OpenMevLibrary.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal > amountBDesired) {
uint256 amountAOptimal = OpenMevLibrary.quote(amountBDesired, reserveB, reserveA);
if (amountAOptimal > amountADesired) revert InsufficientAAmount();
if (amountAOptimal < amountAMin) revert InsufficientAAmount();
(amountA, amountB) = (amountAOptimal, amountBDesired);
} else {
if (amountBOptimal < amountBMin) revert InsufficientBAmount();
(amountA, amountB) = (amountADesired, amountBOptimal);
}
}
} |
@notice Adds liquidity to an ERC-20⇄ERC-20 pool. msg.sender should have already given the router an allowance of at least amountADesired/amountBDesired on tokenA/tokenB |
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
)
external
virtual
returns (
uint256 amountA,
uint256 amountB,
uint256 liquidity
)
{
ensure(deadline);
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, tokenA, tokenB);
ERC20(tokenA).safeTransferFrom(msg.sender, pair, amountA);
ERC20(tokenB).safeTransferFrom(msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
} |
@notice Adds liquidity to an ERC-20⇄WETH pool with ETH. msg.sender should have already given the router an allowance of at least amountTokenDesired on token. msg.value is treated as a amountETHDesired. Leftover ETH, if any, is returned to msg.sender |
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
virtual
returns (
uint256 amountToken,
uint256 amountETH,
uint256 liquidity
)
{
ensure(deadline);
address weth = WETH09;
(amountToken, amountETH) = _addLiquidity(
token,
weth,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, token, weth);
ERC20(token).safeTransferFrom(msg.sender, pair, amountToken);
IWETH(weth).deposit{ value: amountETH }();
ERC20(weth).safeTransfer(pair, amountETH);
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) SafeTransferLib.safeTransferETH(msg.sender, msg.value - amountETH);
} |
**** REMOVE LIQUIDITY ****
@notice Removes liquidity from an ERC-20⇄ERC-20 pool. msg.sender should have already given the router an allowance of at least liquidity on the pool. |
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) public virtual returns (uint256 amountA, uint256 amountB) {
ensure(deadline);
address pair = OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, tokenA, tokenB);
ERC20(pair).safeTransferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(to);
(address token0, ) = OpenMevLibrary.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
if (amountA < amountAMin) revert InsufficientAAmount();
if (amountB < amountBMin) revert InsufficientBAmount();
} |
@notice Removes liquidity from an ERC-20⇄WETH pool and receive ETH. msg.sender should have already given the router an allowance of at least liquidity on the pool. |
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) public virtual returns (uint256 amountToken, uint256 amountETH) {
address weth = WETH09;
uint256 balanceBefore = ERC20(token).balanceOf(address(this));
(amountToken, amountETH) = removeLiquidity(
token,
weth,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
// exploit check from fee-on-transfer tokens
if (amountToken != ERC20(token).balanceOf(address(this)) - balanceBefore) revert TokenIsFeeOnTransfer();
ERC20(token).safeTransfer(to, amountToken);
IWETH(weth).withdraw(amountETH);
SafeTransferLib.safeTransferETH(to, amountETH);
} |
@notice Removes liquidity from an ERC-20⇄ERC-20 pool without pre-approval, thanks to permit. |
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external virtual returns (uint256 amountA, uint256 amountB) {
IUniswapV2Pair(OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, tokenA, tokenB)).permit(
msg.sender,
address(this),
approveMax ? type(uint256).max : liquidity,
deadline,
v,
r,
s
);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
} |
@notice Removes liquidity from an ERC-20⇄WETTH pool and receive ETH without pre-approval, thanks to permit |
function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external virtual returns (uint256 amountToken, uint256 amountETH) {
IUniswapV2Pair(OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, token, WETH09)).permit(
msg.sender,
address(this),
approveMax ? type(uint256).max : liquidity,
deadline,
v,
r,
s
);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
} |
@notice Identical to removeLiquidityETH, but succeeds for tokens that take a fee on transfer. msg.sender should have already given the router an allowance of at least liquidity on the pool. |
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) public virtual returns (uint256 amountETH) {
address weth = WETH09;
uint256 balanceBefore = ERC20(token).balanceOf(address(this));
(, amountETH) = removeLiquidity(token, weth, liquidity, amountTokenMin, amountETHMin, address(this), deadline);
ERC20(token).safeTransfer(to, ERC20(token).balanceOf(address(this)) - balanceBefore);
IWETH(weth).withdraw(amountETH);
SafeTransferLib.safeTransferETH(to, amountETH);
} |
@notice Identical to removeLiquidityETHWithPermit, but succeeds for tokens that take a fee on transfer. |
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external virtual returns (uint256 amountETH) {
IUniswapV2Pair(OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, token, WETH09)).permit(
msg.sender,
address(this),
approveMax ? type(uint256).max : liquidity,
deadline,
v,
r,
s
);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token,
liquidity,
amountTokenMin,
amountETHMin,
to,
deadline
);
} |
@notice Internal core swap. Requires the initial amount to have already been sent to the first pair. |
function _swap(address _to, OpenMevLibrary.Swap[] memory swaps)
internal
virtual
returns (uint256[] memory amounts)
{
uint256 length = swaps.length;
amounts = new uint256[](_inc(length));
amounts[0] = swaps[0].amountIn;
for (uint256 i; i < length; i = _inc(i)) {
uint256 amountOut = swaps[i].amountOut;
{
(uint256 amount0Out, uint256 amount1Out) = swaps[i].isReverse
? (amountOut, uint256(0))
: (uint256(0), amountOut);
address to = i < _dec(length) ? swaps[_inc(i)].pair : _to;
address pair = swaps[i].pair;
_asmSwap(pair, amount0Out, amount1Out, to);
}
amounts[_inc(i)] = amountOut;
}
} |
@notice Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path. The first element of path is the input token, the last is the output token, and any intermediate elements represent intermediate pairs to trade through. msg.sender should have already given the router an allowance of at least amountIn on the input token. |
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external virtual returns (uint256[] memory amounts) {
ensure(deadline);
address factory = OpenMevLibrary.SUSHI_FACTORY;
OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
uint256 length = swaps.length;
if (swaps[_dec(length)].amountOut < amountOutMin) {
// Change 1 -> fallback for insufficient output amount, check backup router
factory = BACKUP_FACTORY;
swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
if (swaps[_dec(length)].amountOut < amountOutMin) revert InsufficientOutputAmount();
}
ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, amountIn);
amounts = _swap(to, swaps);
// Change 2 -> back-run swaps
_backrunSwaps(factory, swaps);
} |
@notice Receive an exact amount of output tokens for as few input tokens as possible, along the route determined by the path. msg.sender should have already given the router an allowance of at least amountInMax on the input token. |
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external virtual returns (uint256[] memory amounts) {
ensure(deadline);
address factory = OpenMevLibrary.SUSHI_FACTORY;
// amounts = OpenMevLibrary.getAmountsIn(factory, amountOut, path);
OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
if (swaps[0].amountIn > amountInMax) {
factory = BACKUP_FACTORY;
// Change 1 -> fallback for insufficient output amount, check backup router
swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
if (swaps[0].amountIn > amountInMax) revert ExcessiveInputAmount();
}
ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, swaps[0].amountIn);
amounts = _swap(to, swaps);
// Change 2 -> back-run swaps
_backrunSwaps(factory, swaps);
} |
@notice Swaps an exact amount of ETH for as many output tokens as possible, along the route determined by the path. The first element of path must be WETH, the last is the output token. amountIn = msg.value |
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable virtual returns (uint256[] memory amounts) {
ensure(deadline);
address weth = WETH09;
if (path[0] != weth) revert InvalidPath();
address factory = OpenMevLibrary.SUSHI_FACTORY;
OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsOut(factory, msg.value, path);
uint256 length = swaps.length;
if (swaps[_dec(length)].amountOut < amountOutMin) {
factory = BACKUP_FACTORY;
// Change 1 -> fallback for insufficient output amount, check backup router
swaps = OpenMevLibrary.getSwapsOut(factory, msg.value, path);
if (swaps[_dec(length)].amountOut < amountOutMin) revert InsufficientOutputAmount();
}
IWETH(weth).deposit{ value: msg.value }();
ERC20(weth).safeTransfer(swaps[0].pair, swaps[0].amountIn);
amounts = _swap(to, swaps);
// Change 2 -> back-run swaps
_backrunSwaps(factory, swaps);
} |
@notice Receive an exact amount of ETH for as few input tokens as possible, along the route determined by the path. The first element of path is the input token, the last must be WETH. msg.sender should have already given the router an allowance of at least amountInMax on the input token. |
function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external virtual returns (uint256[] memory amounts) {
ensure(deadline);
address weth = WETH09;
if (path[_dec(path.length)] != weth) revert InvalidPath();
address factory = OpenMevLibrary.SUSHI_FACTORY;
OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
if (swaps[0].amountIn > amountInMax) {
factory = BACKUP_FACTORY;
// Change 1 -> fallback for insufficient output amount, check backup router
swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
if (swaps[0].amountIn > amountInMax) revert ExcessiveInputAmount();
}
ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, swaps[0].amountIn);
amounts = _swap(address(this), swaps);
IWETH(weth).withdraw(amountOut);
SafeTransferLib.safeTransferETH(to, amountOut);
// Change 2 -> back-run swaps
_backrunSwaps(factory, swaps);
} |
@notice Swaps an exact amount of tokens for as much ETH as possible, along the route determined by the path. The first element of path is the input token, the last must be WETH. |
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external virtual returns (uint256[] memory amounts) {
ensure(deadline);
address weth = WETH09;
if (path[_dec(path.length)] != weth) revert InvalidPath();
address factory = OpenMevLibrary.SUSHI_FACTORY;
OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
uint256 length = swaps.length;
if (swaps[_dec(length)].amountOut < amountOutMin) {
factory = BACKUP_FACTORY;
// Change 1 -> fallback for insufficient output amount, check backup router
swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
if (swaps[_dec(length)].amountOut < amountOutMin) revert InsufficientOutputAmount();
}
ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, amountIn);
amounts = _swap(address(this), swaps);
uint256 amountOut = swaps[_dec(length)].amountOut;
IWETH(weth).withdraw(amountOut);
SafeTransferLib.safeTransferETH(to, amountOut);
// Change 2 -> back-run swaps
_backrunSwaps(factory, swaps);
} |
@notice Receive an exact amount of tokens for as little ETH as possible, along the route determined by the path. The first element of path must be WETH. Leftover ETH, if any, is returned to msg.sender. amountInMax = msg.value |
function swapETHForExactTokens(
uint256 amountOut,
address[] calldata path,
address to,
uint256 deadline
) external payable virtual returns (uint256[] memory amounts) {
ensure(deadline);
address weth = WETH09;
if (path[0] != weth) revert InvalidPath();
address factory = OpenMevLibrary.SUSHI_FACTORY;
OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
if (swaps[0].amountIn > msg.value) {
factory = BACKUP_FACTORY;
// Change 1 -> fallback for insufficient output amount, check backup router
swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
if (swaps[0].amountIn > msg.value) revert ExcessiveInputAmount();
}
IWETH(weth).deposit{ value: swaps[0].amountIn }();
ERC20(weth).safeTransfer(swaps[0].pair, swaps[0].amountIn);
amounts = _swap(to, swaps);
// refund dust eth, if any
if (msg.value > swaps[0].amountIn) SafeTransferLib.safeTransferETH(msg.sender, msg.value - swaps[0].amountIn);
// Change 2 -> back-run swaps
_backrunSwaps(factory, swaps);
}
//requires the initial amount to have already been sent to the first pair
function _swapSupportingFeeOnTransferTokensExecute(
address pair,
uint256 amountOutput,
bool isReverse,
address to
) internal virtual {
(uint256 amount0Out, uint256 amount1Out) = isReverse ? (amountOutput, uint256(0)) : (uint256(0), amountOutput);
_asmSwap(pair, amount0Out, amount1Out, to);
}
function _swapSupportingFeeOnTransferTokens(
address[] memory path,
address _to,
address factory
) internal virtual {
uint256 length = path.length;
for (uint256 i; i < _dec(length); i = _inc(i)) {
(address tokenIn, address tokenOut) = (path[i], path[_inc(i)]);
bool isReverse;
address pair;
{
(address token0, address token1) = OpenMevLibrary.sortTokens(tokenIn, tokenOut);
isReverse = tokenOut == token0;
pair = OpenMevLibrary._asmPairFor(factory, token0, token1);
}
uint256 amountOutput;
{
// scope to avoid stack too deep errors
uint256 amountInput;
(uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pair).getReserves();
(uint112 reserveInput, uint112 reserveOutput) = isReverse ? (reserve1, reserve0) : (reserve0, reserve1);
amountInput = ERC20(tokenIn).balanceOf(pair) - reserveInput;
amountOutput = OpenMevLibrary.getAmountOut(amountInput, reserveInput, reserveOutput);
}
address to = i < length - 2 ? OpenMevLibrary.pairFor(factory, tokenOut, path[i + 2]) : _to;
_swapSupportingFeeOnTransferTokensExecute(pair, amountOutput, isReverse, to);
}
} |
@notice Identical to swapExactTokensForTokens, but succeeds for tokens that take a fee on transfer. msg.sender should have already given the router an allowance of at least amountIn on the input token. |
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external virtual {
ensure(deadline);
address factory = OpenMevLibrary.SUSHI_FACTORY;
ERC20(path[0]).safeTransferFrom(msg.sender, OpenMevLibrary.pairFor(factory, path[0], path[1]), amountIn);
uint256 balanceBefore = ERC20(path[_dec(path.length)]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to, factory);
if (ERC20(path[_dec(path.length)]).balanceOf(to) - balanceBefore < amountOutMin)
revert InsufficientOutputAmount();
} |
@notice Identical to swapExactETHForTokens, but succeeds for tokens that take a fee on transfer. amountIn = msg.value |
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable virtual {
ensure(deadline);
address weth = WETH09;
if (path[0] != weth) revert InvalidPath();
address factory = OpenMevLibrary.SUSHI_FACTORY;
uint256 amountIn = msg.value;
IWETH(weth).deposit{ value: amountIn }();
ERC20(weth).safeTransfer(OpenMevLibrary.pairFor(factory, path[0], path[1]), amountIn);
uint256 balanceBefore = ERC20(path[_dec(path.length)]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to, factory);
if (ERC20(path[_dec(path.length)]).balanceOf(to) - balanceBefore < amountOutMin)
revert InsufficientOutputAmount();
} |
@notice Identical to swapExactTokensForETH, but succeeds for tokens that take a fee on transfer. |
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external virtual {
ensure(deadline);
address weth = WETH09;
if (path[_dec(path.length)] != weth) revert InvalidPath();
address factory = OpenMevLibrary.SUSHI_FACTORY;
ERC20(path[0]).safeTransferFrom(msg.sender, OpenMevLibrary.pairFor(factory, path[0], path[1]), amountIn);
uint256 balanceBefore = ERC20(weth).balanceOf(address(this));
_swapSupportingFeeOnTransferTokens(path, address(this), factory);
uint256 amountOut = ERC20(weth).balanceOf(address(this)) - balanceBefore;
if (amountOut < amountOutMin) revert InsufficientOutputAmount();
IWETH(weth).withdraw(amountOut);
SafeTransferLib.safeTransferETH(to, amountOut);
}
function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) external pure virtual returns (uint256 amountB) {
return OpenMevLibrary.quote(amountA, reserveA, reserveB);
}
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external pure virtual returns (uint256 amountOut) {
return OpenMevLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) external pure virtual returns (uint256 amountIn) {
return OpenMevLibrary.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
virtual
returns (uint256[] memory amounts)
{
return OpenMevLibrary.getAmountsOut(OpenMevLibrary.SUSHI_FACTORY, amountIn, path);
}
function getAmountsIn(uint256 amountOut, address[] calldata path)
external
view
virtual
returns (uint256[] memory amounts)
{
return OpenMevLibrary.getAmountsIn(OpenMevLibrary.SUSHI_FACTORY, amountOut, path);
} |
@notice Internal call to back-run swaps i.e. extract natural MEV at source. |
function _backrunSwaps(address factory, OpenMevLibrary.Swap[] memory swaps) internal {
uint256 length = swaps.length;
for (uint256 i; i < length; i = _inc(i)) {
if (swaps[i].isBackrunnable) {
(address input, address output) = (swaps[i].tokenIn, swaps[i].tokenOut);
bool isAaveAsset = IS_AAVE_ASSET[output];
uint256 contractAssetBalance = ERC20(output).balanceOf(address(this));
uint256 bentoBalance = ERC20(output).balanceOf(BENTO);
if (_isNonZero(contractAssetBalance) || isAaveAsset || _isNonZero(bentoBalance)) {
address factory1 = factory == OpenMevLibrary.SUSHI_FACTORY
? BACKUP_FACTORY
: OpenMevLibrary.SUSHI_FACTORY;
address pair1 = address(IUniswapV2Factory(factory1).getPair(input, output));
if (pair1 != address(0)) {
(uint256 optimalAmount, uint256 optimalReturns) = OpenMevLibrary.getOptimalAmounts(
swaps[i].pair,
pair1,
swaps[i].isReverse,
isAaveAsset,
contractAssetBalance,
bentoBalance
);
if (optimalReturns > uint256(0)) {
if (contractAssetBalance >= optimalAmount) {
{
uint256 amountOut = _arb(
factory,
factory1,
input,
output,
address(this),
optimalAmount
);
if (amountOut < optimalAmount) revert InsufficientOutputAmount();
}
emit MEV(msg.sender, output, optimalReturns);
} else {
// kashi flashloan requires extra hurdle of interest @ 0.05% of loan value and sufficient balance
if (optimalReturns > ((optimalAmount * 5) / 10000) && bentoBalance >= optimalAmount) {
_flashSwapKashi(factory, factory1, input, output, optimalAmount, optimalReturns);
} else if (optimalReturns > ((optimalAmount * 9) / 10000)) {
// aave flashloan requires extra hurdle of interest @ 0.09% of loan value (https://docs.aave.com/developers/guides/flash-loans)
// check available liquidity for aave asset
if (
IProtocolDataProvider(AAVE_DATA_PROVIDER).getReserveData(output) > optimalAmount
) {
_flashSwap(factory, factory1, input, output, optimalAmount, optimalReturns);
}
}
}
}
}
}
}
}
} |
@notice Internal call to perform multiple swaps across multiple dexes with a BentoBox flashloan. |
function _flashSwapKashi(
address factory,
address factory1,
address input,
address output,
uint256 amountIn,
uint256 optimalReturns
) internal {
bytes memory params = _encode(factory, factory1, input);
// try / catch flashloan arb. In case arb reverts, user swap will still succeed.
try bento.flashLoan(IFlashBorrower(address(this)), address(this), output, amountIn, params) {
// success
emit MEV(msg.sender, output, optimalReturns - ((amountIn * 5) / 10000));
} catch {
// fail flashloan
emit LoanError(output, amountIn);
}
} |
@notice Called from BentoBox Lending pool after contract has received the flash loaned amount |
function onFlashLoan(
address sender,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external {
if (msg.sender != BENTO) revert ExecuteNotAuthorized();
uint256 amountOver;
{
(address factory, address factory1, address input) = _decode(data);
amountOver = _arb(factory, factory1, input, token, sender, amount);
}
uint256 amountOwing = amount + fee;
if (amountOver <= amountOwing) revert InsufficientOutputAmount();
ERC20(token).safeTransfer(BENTO, amountOwing);
} |
@notice Internal call to perform multiple swaps across multiple dexes with an Aave flashloan. |
function _flashSwap(
address factory,
address factory1,
address input,
address output,
uint256 amountIn,
uint256 optimalReturns
) internal {
// address of the contract receiving the funds
address receiverAddress = address(this);
// addresses of the reserves to flashloan
address[] memory assets = new address[](1);
assets[0] = output;
// amounts of assets to flashloan.
uint256[] memory amounts = new uint256[](1);
amounts[0] = amountIn;
// 0 = no debt (just revert), 1 = stable, 2 = variable
uint256[] memory modes = new uint256[](1);
modes[0] = 0;
// compress our 3 addresses
bytes memory params = _encode(factory, factory1, input);
// try / catch flashloan arb. In case arb reverts, user swap will still succeed.
try
ILendingPool(LENDING_POOL_ADDRESS).flashLoan(
receiverAddress,
assets,
amounts,
modes,
receiverAddress,
params,
uint16(0)
)
{
// success
emit MEV(msg.sender, output, optimalReturns - ((amountIn * 9) / 10000));
} catch {
// fail
emit LoanError(output, amountIn);
}
} |
@notice Called from Aave Lending pool after contract has received the flash loaned amount (https://docs.aave.com/developers/v/2.0/guides/flash-loans) |
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
if (msg.sender != LENDING_POOL_ADDRESS) revert ExecuteNotAuthorized();
address asset = assets[0];
uint256 amountOver;
{
uint256 amountIn = amounts[0];
(address factory, address factory1, address input) = _decode(params);
amountOver = _arb(factory, factory1, input, asset, initiator, amountIn);
}
uint256 amountOwing = amounts[0] + premiums[0];
if (amountOver <= amountOwing) revert InsufficientOutputAmount();
ERC20(asset).safeApprove(LENDING_POOL_ADDRESS, amountOwing);
return true;
} |
@notice Internal call to perform single swap |
function _asmSwap(
address pair,
uint256 amount0Out,
uint256 amount1Out,
address to
) internal {
bytes4 selector = SWAP_SELECTOR;
assembly {
let ptr := mload(0x40) // get free memory pointer
mstore(ptr, selector) // append 4 byte selector
mstore(add(ptr, 0x04), amount0Out) // append amount0Out
mstore(add(ptr, 0x24), amount1Out) // append amount1Out
mstore(add(ptr, 0x44), to) // append to
mstore(add(ptr, 0x64), 0x80) // append location of byte list
mstore(add(ptr, 0x84), 0) // append 0 bytes data
let success := call(
gas(), // gas remaining
pair, // destination address
0, // 0 value
ptr, // input buffer
0xA4, // input length
0, // output buffer
0 // output length
)
}
} |
@notice Internal call to perform single cross-dex state arbitrage |
function _arb(
address factory,
address factory1,
address input,
address output,
address to,
uint256 amountIn
) internal returns (uint256 amountOut) {
// first swap output -> input (factory)
address pair;
bool isReverse;
{
(address token0, address token1) = OpenMevLibrary.sortTokens(output, input);
pair = OpenMevLibrary._asmPairFor(factory, token0, token1);
isReverse = output == token0;
}
{
(uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pair).getReserves();
(uint112 reserveIn, uint112 reserveOut) = isReverse ? (reserve0, reserve1) : (reserve1, reserve0);
amountOut = OpenMevLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
}
ERC20(output).safeTransfer(pair, amountIn);
(uint256 amount0Out, uint256 amount1Out) = isReverse ? (uint256(0), amountOut) : (amountOut, uint256(0));
_asmSwap(pair, amount0Out, amount1Out, to);
// next swap input -> ouput (factory1)
amountIn = amountOut;
pair = OpenMevLibrary._asmPairFor(factory1, isReverse ? output : input, isReverse ? input : output);
{
(uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pair).getReserves();
(uint112 reserveIn, uint112 reserveOut) = isReverse ? (reserve1, reserve0) : (reserve0, reserve1);
amountOut = OpenMevLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
}
ERC20(input).safeTransfer(pair, amountIn);
(amount0Out, amount1Out) = isReverse ? (amountOut, uint256(0)) : (uint256(0), amountOut);
_asmSwap(pair, amount0Out, amount1Out, to);
} |
@notice Multi-sig consensus call to distribute a given percentage of specified tokens to specified receivers. |
function harvest(
uint256 percentage,
address[] calldata tokens,
address[] calldata receivers
) external payable onlyOwner {
if (percentage > 100 || _isZero(percentage)) revert NotPercent();
uint256 numReceivers = receivers.length;
if (_isZero(numReceivers)) revert NoReceivers();
uint256 numTokens = tokens.length;
if (_isZero(numTokens)) revert NoTokens();
uint256 balanceToDistribute;
for (uint256 i; i < numTokens; i = _inc(i)) {
address token = tokens[i];
balanceToDistribute = (ERC20(token).balanceOf(address(this)) * percentage) / (100 * numReceivers);
if (_isNonZero(balanceToDistribute)) {
for (uint256 j; j < numReceivers; j = _inc(j)) {
ERC20(token).safeTransfer(receivers[j], balanceToDistribute);
}
}
}
} |
@notice Update internal Aave asset flag |
function updateAaveAsset(bool isActive, address asset) external payable onlyOwner {
IS_AAVE_ASSET[asset] = isActive;
} |
@noticeUpdate all internal Aave assets |
function updateAllAaveAssets() external payable onlyOwner {
address[] memory aaveAssets = ILendingPool(LENDING_POOL_ADDRESS).getReservesList();
uint256 length = aaveAssets.length;
for (uint256 i; i < length; i = _inc(i)) {
address asset = aaveAssets[i];
IS_AAVE_ASSET[asset] = true;
}
} |
@notice Compresses 3 addresses into byte stream (len = 60) |
function _encode(
address a,
address b,
address c
) internal pure returns (bytes memory) {
bytes memory data = new bytes(60);
assembly {
mstore(add(data, 32), shl(96, a))
mstore(add(data, 52), shl(96, b))
mstore(add(data, 72), shl(96, c))
}
return data;
} |
@notice De-compresses 3 addresses from byte stream (len = 60) |
function _decode(bytes memory data)
internal
pure
returns (
address a,
address b,
address c
)
{
assembly {
a := mload(add(data, 20))
b := mload(add(data, 40))
c := mload(add(data, 60))
}
} |
@notice Uint256 zero check gas saver |
function _isZero(uint256 value) internal pure returns (bool boolValue) {
assembly {
boolValue := iszero(value)
}
} |
@notice Uint256 not zero check gas saver |
function _isNonZero(uint256 value) internal pure returns (bool boolValue) {
assembly {
boolValue := iszero(iszero(value))
}
} |
@notice Unchecked increment gas saver for loops |
function _inc(uint256 i) internal pure returns (uint256) {
unchecked {
return i + 1;
}
} |
@notice Unchecked decrement gas saver for loops |
function _dec(uint256 i) internal pure returns (uint256) {
unchecked {
return i - 1;
}
} |
@noticeFunction to receive Ether. msg.data must be empty |
receive() external payable {} |
@noticeFallback function is called when msg.data is not empty |
fallback() external payable {}
}
|