diff --git a/.gitmodules b/.gitmodules index 888d42d..bd8a6ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,15 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/balancer-v2-monorepo"] + path = lib/balancer-v2-monorepo + url = https://github.com/balancer/balancer-v2-monorepo +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/v3-periphery"] + path = lib/v3-periphery + url = https://github.com/uniswap/v3-periphery +[submodule "lib/v3-core"] + path = lib/v3-core + url = https://github.com/uniswap/v3-core diff --git a/foundry.toml b/foundry.toml index 25b918f..d923a7e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,11 @@ src = "src" out = "out" libs = ["lib"] - +remappings = [ + "forge-std/=lib/forge-std/src/", + "@balancer/balancer-v2-monorepo/pkg/=lib/balancer-v2-monorepo/pkg/", + "@openzeppelin/openzeppelin-contracts/contracts/=lib/openzeppelin-contracts/contracts/", + "@uniswap/v3-periphery/contracts/interfaces=lib/v3-periphery/contracts/interfaces", + "@uniswap/v3-core/contracts/interfaces/=lib/v3-core/contracts/interfaces/", +] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/balancer-v2-monorepo b/lib/balancer-v2-monorepo new file mode 160000 index 0000000..36d2823 --- /dev/null +++ b/lib/balancer-v2-monorepo @@ -0,0 +1 @@ +Subproject commit 36d282374b457dddea828be7884ee0d185db06ba diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..281550b --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 281550b71c3df9a83e6b80ceefc700852c287570 diff --git a/lib/v3-core b/lib/v3-core new file mode 160000 index 0000000..e3589b1 --- /dev/null +++ b/lib/v3-core @@ -0,0 +1 @@ +Subproject commit e3589b192d0be27e100cd0daaf6c97204fdb1899 diff --git a/lib/v3-periphery b/lib/v3-periphery new file mode 160000 index 0000000..80f26c8 --- /dev/null +++ b/lib/v3-periphery @@ -0,0 +1 @@ +Subproject commit 80f26c86c57b8a5e4b913f42844d4c8bd274d058 diff --git a/src/Arbitrage.sol b/src/Arbitrage.sol new file mode 100644 index 0000000..eba8a0b --- /dev/null +++ b/src/Arbitrage.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "@balancer/balancer-v2-monorepo/pkg/interfaces/contracts/vault/IVault.sol"; +import "@balancer/balancer-v2-monorepo/pkg/interfaces/contracts/vault/IFlashLoanRecipient.sol"; +import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; + +contract Arbitrage is IFlashLoanRecipient { + IVault private constant vault = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); + + address public owner; + + struct Trade { + address[] routerPath; + address[] tokenPath; + uint24 fee; + } + + constructor() { + owner = msg.sender; + } + + function executeTrade(address[] memory _routerPath, address[] memory _tokenPath, uint24 _fee, uint256 _flashAmount) + external + { + bytes memory data = abi.encode(Trade({routerPath: _routerPath, tokenPath: _tokenPath, fee: _fee})); + + // Token to flash loan, by default we are flash loaning 1 token. + IERC20[] memory tokens = new IERC20[](1); + tokens[0] = IERC20(_tokenPath[0]); + + // Flash loan amount. + uint256[] memory amounts = new uint256[](1); + amounts[0] = _flashAmount; + + vault.flashLoan(this, tokens, amounts, data); + } + + function receiveFlashLoan( + IERC20[] memory tokens, + uint256[] memory amounts, + uint256[] memory feeAmounts, + bytes memory userData + ) external override { + require(msg.sender == address(vault)); + + // Decode our swap data so we can use it + Trade memory trade = abi.decode(userData, (Trade)); + uint256 flashAmount = amounts[0]; + + // Since balancer called this function, we should have funds to begin swapping... + + // We perform the 1st swap. + // We swap the flashAmount of token0 and expect to get X amount of token1 + _swapOnV3(trade.routerPath[0], trade.tokenPath[0], flashAmount, trade.tokenPath[1], 0, trade.fee); + + // We perform the 2nd swap. + // We swap the contract balance of token1 and + // expect to at least get the flashAmount of token0 + _swapOnV3( + trade.routerPath[1], + trade.tokenPath[1], + IERC20(trade.tokenPath[1]).balanceOf(address(this)), + trade.tokenPath[0], + flashAmount, + trade.fee + ); + + // Transfer back what we flash loaned + IERC20(trade.tokenPath[0]).transfer(address(vault), flashAmount); + + // Transfer any excess tokens [i.e. profits] to owner + IERC20(trade.tokenPath[0]).transfer(owner, IERC20(trade.tokenPath[0]).balanceOf(address(this))); + } + + // -- INTERNAL FUNCTIONS -- // + + function _swapOnV3( + address _router, + address _tokenIn, + uint256 _amountIn, + address _tokenOut, + uint256 _amountOut, + uint24 _fee + ) internal { + // Approve token to swap + IERC20(_tokenIn).approve(_router, _amountIn); + + // Setup swap parameters + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ + tokenIn: _tokenIn, + tokenOut: _tokenOut, + fee: _fee, + recipient: address(this), + deadline: block.timestamp, + amountIn: _amountIn, + amountOutMinimum: _amountOut, + sqrtPriceLimitX96: 0 + }); + + // Perform swap + ISwapRouter(_router).exactInputSingle(params); + } +} diff --git a/src/ArbitrageMaker.sol b/src/ArbitrageMaker.sol deleted file mode 100644 index 8b13789..0000000 --- a/src/ArbitrageMaker.sol +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/FlashLoanTx.sol b/src/FlashLoanTx.sol deleted file mode 100644 index 8b13789..0000000 --- a/src/FlashLoanTx.sol +++ /dev/null @@ -1 +0,0 @@ -