β MyMevBot
The setup is there are two uni v2 pairs:
weth/usdc pair, price is balanced
weth/usdt pair, price is unbalanced <- we arbitrage from this pool
The weth/usdt pair is unbalanced beacause in test file a user 0xBeeb provided liquidity (10 weth, 3000000 usdt). This position is under 1 weth = 300000 usdt, which makes the price of weth goes a lot higher than market price.
We are given a flashloan service for usdc, so the arbitrage path is:
Flashloan usdc
Go to weth/usdc pool to swap for weth
Go to weth/usdt pool to swap for usdt <- in this step we get a lot more usdt than usual
Let router find a pair to swap usdt for usdc, then we pay back the flashloan and keep the profit.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./interfaces/IERC20.sol";
/**
*
* ARBITRAGE A POOL
*
* Given two pools where the token pair represents the same underlying; WETH/USDC and WETH/USDT (the formal has the correct price, while the latter doesnt).
* The challenge is to flash borrow some USDC (>1000) from `flashLenderPool` to arbitrage the pool(s), then make profit by ensuring MyMevBot contract's USDC balance
* is more than 0.
*
*/
contract MyMevBot {
IUniswapV3Pool public immutable flashLenderPool;
IERC20 public immutable weth;
IERC20 public immutable usdc;
IERC20 public immutable usdt;
IUniswapV2Router public immutable router;
bool public flashLoaned;
constructor(address _flashLenderPool, address _weth, address _usdc, address _usdt, address _router) {
flashLenderPool = IUniswapV3Pool(_flashLenderPool);
weth = IERC20(_weth);
usdc = IERC20(_usdc);
usdt = IERC20(_usdt);
router = IUniswapV2Router(_router);
}
function performArbitrage() public {
// Start with the minimum required amount
flashLenderPool.flash(address(this), 1000e6, 0, "");
}
function uniswapV3FlashCallback(uint256 _fee0, uint256, bytes calldata data) external {
callMeCallMe();
uint256 usdcAmount = usdc.balanceOf(address(this));
// Approve router to spend tokens
usdc.approve(address(router), usdcAmount);
weth.approve(address(router), type(uint256).max);
usdt.approve(address(router), type(uint256).max);
// Simple arbitrage: USDC -> WETH -> USDT -> USDC
// Use the entire flash loan amount for arbitrage
address[] memory path = new address[](4);
path[0] = address(usdc);
path[1] = address(weth);
path[2] = address(usdt);
path[3] = address(usdc);
router.swapExactTokensForTokens(
usdcAmount, // Use full amount for arbitrage
0,
path,
address(this),
block.timestamp + 300
);
// Pay back the flash loan (borrowed amount + fee)
uint256 repayAmount = usdcAmount + _fee0;
usdc.transfer(address(flashLenderPool), repayAmount);
}
function callMeCallMe() private {
uint256 usdcBal = usdc.balanceOf(address(this));
require(msg.sender == address(flashLenderPool), "not callback");
require(flashLoaned = usdcBal >= 1000e6, "FlashLoan less than 1,000 USDC.");
}
}
interface IUniswapV3Pool {
/**
* recipient: the address which will receive the token0 and/or token1 amounts.
* amount0: the amount of USDC to borrow.
* amount1: the amount of WETH to borrow.
* data: any data to be passed through to the callback.
*/
function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
}
interface IUniswapV2Router {
/**
* amountIn: the amount to use for swap.
* amountOutMin: the minimum amount of output tokens that must be received for the transaction not to revert.
* path: an array of token addresses. In our case, WETH and USDC.
* to: recipient address to receive the liquidity tokens.
* deadline: timestamp after which the transaction will revert.
*/
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
}
Last updated