From c3cd81394fea33fb4aa055c53d5e948eed608ea8 Mon Sep 17 00:00:00 2001 From: manavjain2002 Date: Sun, 15 Oct 2023 13:54:15 +0530 Subject: [PATCH] Created optimized arbitrage-bot smart contract code --- Level1/Dex Arbitrage/Arbitrage.sol | 157 ++++++++++++++++++ Level1/Dex Arbitrage/README.md | 78 +++++++++ Level1/Dex Arbitrage/interfaces/IERC20.sol | 79 +++++++++ .../interfaces/IUniswapV2Router02.sol | 17 ++ Level1/Dex Arbitrage/interfaces/IWETH9.sol | 24 +++ 5 files changed, 355 insertions(+) create mode 100644 Level1/Dex Arbitrage/Arbitrage.sol create mode 100644 Level1/Dex Arbitrage/README.md create mode 100644 Level1/Dex Arbitrage/interfaces/IERC20.sol create mode 100644 Level1/Dex Arbitrage/interfaces/IUniswapV2Router02.sol create mode 100644 Level1/Dex Arbitrage/interfaces/IWETH9.sol diff --git a/Level1/Dex Arbitrage/Arbitrage.sol b/Level1/Dex Arbitrage/Arbitrage.sol new file mode 100644 index 0000000..7331e04 --- /dev/null +++ b/Level1/Dex Arbitrage/Arbitrage.sol @@ -0,0 +1,157 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./interfaces/IERC20.sol"; +import "./interfaces/IWETH9.sol"; +import "./interfaces/IUniswapV2Router02.sol"; + +contract Arb is Ownable, ReentrancyGuard { + address payable private wethAddress; + + constructor(address _wethAddress) + // Ownable(msg.sender) + // Uncomment above line if you get this error - No arguments passed to the base constructor. Specify the arguments or mark "Arb" as abstract + { + wethAddress = payable(_wethAddress); + } + + function _swapTokens( + address _router, + address _tokenIn, + address _tokenOut, + uint256 _amountIn, + uint256 _amountOutMin, + uint256 _deadline + ) private { + IUniswapV2Router02 routerContract = IUniswapV2Router02(_router); + IERC20(_tokenIn).approve(_router, _amountIn); + address[] memory path = new address[](2); + path[0] = _tokenIn; + path[1] = _tokenOut; + routerContract.swapExactTokensForTokens( + _amountIn, + _amountOutMin, + path, + address(this), + _deadline + ); + } + + function getExpectedAmountOut( + address _router, + address[] memory path, + uint256 _amountIn + ) public view returns (uint256) { + IUniswapV2Router02 routerContract = IUniswapV2Router02(_router); + uint256[] memory amountOutMins = routerContract.getAmountsOut( + _amountIn, + path + ); + return amountOutMins[path.length - 1]; + } + + function dualDexTrade( + address _router1, + address _router2, + address _token1, + address _token2, + uint256 _amountIn, + uint256 _deadline + ) external onlyOwner() nonReentrant() { + uint256 token1BalanceBeforeSwap = IERC20(_token1).balanceOf( + address(this) + ); + + uint256 token2BalanceBeforeSwap = IERC20(_token2).balanceOf( + address(this) + ); + + address[] memory path = new address[](2); + path[0] = _token1; + path[1] = _token2; + + uint256 expectedToken2Output = getExpectedAmountOut( + _router1, + path, + _amountIn + ); + + _swapTokens( + _router1, + _token1, + _token2, + _amountIn, + expectedToken2Output, + _deadline + ); + + uint256 token2BalanceAfterSwap = IERC20(_token2).balanceOf( + address(this) + ); + + uint256 swappedToken2Balance = token2BalanceAfterSwap - + token2BalanceBeforeSwap; + + path[0] = _token2; + path[1] = _token1; + + uint256 expectedToken1Output = getExpectedAmountOut( + _router2, + path, + swappedToken2Balance + ); + + _swapTokens( + _router2, + _token2, + _token1, + swappedToken2Balance, + expectedToken1Output, + _deadline + ); + + uint256 token1BalanceAfterSwap = IERC20(_token1).balanceOf( + address(this) + ); + + require(token1BalanceAfterSwap > token1BalanceBeforeSwap, "No Profit"); + } + + function _unwrapWETH() private { + uint256 balance = IWETH9(wethAddress).balanceOf(address(this)); + require(balance > 0, "Insufficient balance"); + IWETH9(wethAddress).withdraw(balance); + } + + function withdrawETH() public onlyOwner() nonReentrant() { + try + IWETH9(wethAddress).withdraw( + IWETH9(wethAddress).balanceOf(address(this)) + ) + {} catch {} + + _withdrawETHTo(owner()); + } + + function _withdrawETHTo(address to) private { + uint256 value = address(this).balance; + + (bool success, ) = to.call{value: value}(""); + require(success, "Withdraw failed"); + } + + function unwrapWETH() public onlyOwner() nonReentrant() { + _unwrapWETH(); + } + + function withdrawTokens(address _token) external onlyOwner() nonReentrant() { + uint256 balance = IERC20(_token).balanceOf(address(this)); + IERC20(_token).transfer(owner(), balance); + } + + receive() external payable {} + + fallback() external payable {} +} diff --git a/Level1/Dex Arbitrage/README.md b/Level1/Dex Arbitrage/README.md new file mode 100644 index 0000000..9b18426 --- /dev/null +++ b/Level1/Dex Arbitrage/README.md @@ -0,0 +1,78 @@ +# Arbitrage Bot Smart Contract +#### Aritrage Bot is used to earn the profit by trading the asset having different liquidities in different dexes. + +## How to call dualDexTrade Function ? + +#### Router1 - router address of dex that have less price of token2. +#### Router2 - router address of dex that have high price of token2. +#### Token1 - address of token1 i.e address of your asset(token you hold). +#### Token2 - address of token on which you want to perform the arbitrage. +#### AmountIn - amount of token1 you want to use for arbitrage. (in Wei). +#### Deadline - maximum timestamp to perform the arbitrage trade. + +## For example: + +Network - Goerli + +Address of token1 - 0x7bC40cFa63A04312b954e648c0cCD0c271F86C39 + +Address of token2 - 0x68B379A700d2d2a9Bad1306e41eB34b777aF43ED + +## Added the liquidity in two different dexes i.e Uniswap and Sushiswap + +Uniswap Router Address - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D + +SushiSwap Router Address - 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506 + +#### Liquidity on Uniswap + +Token1 - 100000 +Token2 - 10000000 + +That shows 1 Token1 = 100 Token2 + +#### Liquidity on SushiSwap + +Token1 - 100000 +Token2 - 1000000000 + +That shows 1 Token1 = 10000 Token2 + +#### Let's perform Arbitrage on it. + +Router1 - 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506 + +Router2 - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D + +Token1 - 0x7bC40cFa63A04312b954e648c0cCD0c271F86C39 + +Token2 - 0x68B379A700d2d2a9Bad1306e41eB34b777aF43ED + +AmountIn - 1000000000000000000000 (1000 token1) + +Deadline - Ideally, value is current timestamp + 60. + +## So, the trade will perform in following steps: + +1. Token1 is swapped to token2 on router1. + +2. Token2 is swapped to token1 on router2. + +3. Checking balance of token1 after the above two steps. If balanceAfterTrade is greater than balanceBeforeTrade, the trade will be successful, else it will revert. + + +## So, in our example, + +### Step 1 + +#### amountIn1 = 1000000000000000000000 +#### amountOut1 = 9969006090092817746 + +### Step 2 + +#### amountIn2 = 9969006090092817746 +#### amountOut2 = 99292303114452994728883 + +### Step 3 + +#### amountOut2 - amountIn1 > 0, so, trade succeeded. diff --git a/Level1/Dex Arbitrage/interfaces/IERC20.sol b/Level1/Dex Arbitrage/interfaces/IERC20.sol new file mode 100644 index 0000000..63765d5 --- /dev/null +++ b/Level1/Dex Arbitrage/interfaces/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.9; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} \ No newline at end of file diff --git a/Level1/Dex Arbitrage/interfaces/IUniswapV2Router02.sol b/Level1/Dex Arbitrage/interfaces/IUniswapV2Router02.sol new file mode 100644 index 0000000..7e7502f --- /dev/null +++ b/Level1/Dex Arbitrage/interfaces/IUniswapV2Router02.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +interface IUniswapV2Router02 { + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function getAmountsOut(uint256 amountIn, address[] memory path) + external + view + returns (uint256[] memory amounts); +} \ No newline at end of file diff --git a/Level1/Dex Arbitrage/interfaces/IWETH9.sol b/Level1/Dex Arbitrage/interfaces/IWETH9.sol new file mode 100644 index 0000000..fcde7d1 --- /dev/null +++ b/Level1/Dex Arbitrage/interfaces/IWETH9.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +interface IWETH9 { + function balanceOf(address) external view returns (uint256); + + function allowance(address, address) external view returns (uint256); + + receive() external payable; + + function deposit() external payable; + + function withdraw(uint256 wad) external; + + function approve(address guy, uint256 wad) external returns (bool); + + function transfer(address dst, uint256 wad) external returns (bool); + + function transferFrom( + address src, + address dst, + uint256 wad + ) external returns (bool); +} \ No newline at end of file