Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions script/DeployArbitrage.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@
pragma solidity ^0.8.13;

import {Script, console2} from "forge-std/Script.sol";
import {Arbitrage} from "../src/Arbitrage.sol";
import {FlashArbitrage} from "../src/FlashArbitrage.sol";
import {HelperConfig} from "../script/HelperConfig.s.sol";

contract DeployArbitrage is Script {
HelperConfig public helperConfig;
HelperConfig.NetworkConfig modeConfig;
HelperConfig.ForkNetworkConfig SepoliaConfig;
Arbitrage public arbitrage;
FlashArbitrage public flashArbitrage;

function setUp() public {
helperConfig = new HelperConfig();
modeConfig = helperConfig.getModeSepoliaConfig();
}

function run() public returns (Arbitrage) {
function run() public returns (FlashArbitrage) {
vm.startBroadcast();
arbitrage = new Arbitrage();
flashArbitrage = new FlashArbitrage();
vm.stopBroadcast();

console2.log("Arbitrage contract deployed to:", address(arbitrage));
return arbitrage;
console2.log("Arbitrage contract deployed to:", address(flashArbitrage));
return flashArbitrage;
}
}
4 changes: 2 additions & 2 deletions script/Swap.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {Arbitrage} from "../src/Arbitrage.sol";
import {FlashArbitrage} from "../src/FlashArbitrage.sol";
import {DeployArbitrage} from "../script/DeployArbitrage.s.sol";
import {HelperConfig} from "../script/HelperConfig.s.sol";
import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol";
import {IERC20} from "@openzeppelin/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

contract Swap is Script {
Arbitrage public arbitrage;
FlashArbitrage public arbitrage;
HelperConfig public helperConfig;
HelperConfig.NetworkConfig currentConfig;

Expand Down
123 changes: 0 additions & 123 deletions src/Arbitrage.sol

This file was deleted.

144 changes: 144 additions & 0 deletions src/FlashArbitrage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 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";
import {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol";

/**
* @title FlashArbitrage
* @author FlashArbAI
* @notice Advanced DEX arbitrage executor leveraging Balancer flash loans
* @dev Implements cross-DEX arbitrage strategies using flash loans and optimized swap paths
*/
contract FlashArbitrage is IFlashLoanRecipient {
/// @notice Balancer V2 Vault for flash loan operations
IVault private constant BALANCER_VAULT = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);

address public strategist;

/// @notice Defines parameters for a complete arbitrage operation
struct ArbStrategy {
address[] dexRouters; // Addresses of DEX routers to use
address[] priceQuoters; // Price quoter contracts for each DEX
address[] tradingPath; // Token addresses in trading sequence
uint24 poolFee; // Trading fee tier (3000 = 0.3%)
}

event ArbitrageExecuted(address sourceToken, address targetToken, uint256 inputAmount, uint256 minimumReturn);

constructor() {
strategist = msg.sender;
}

/**
* @notice Initiates an arbitrage operation with flash loan
* @param _dexRouters Array of DEX router addresses for the arbitrage path
* @param _priceQuoters Array of price quoter addresses for each DEX
* @param _tradingPath Array of token addresses in the arbitrage sequence
* @param _poolFee Fee tier for the pools to use
* @param _loanSize Size of the flash loan to initiate
*/
function executeArbitrage(
address[] memory _dexRouters,
address[] memory _priceQuoters,
address[] memory _tradingPath,
uint24 _poolFee,
uint256 _loanSize
) external {
bytes memory strategyData = abi.encode(
ArbStrategy({
dexRouters: _dexRouters,
priceQuoters: _priceQuoters,
tradingPath: _tradingPath,
poolFee: _poolFee
})
);

// Configure flash loan parameters
IERC20[] memory loanTokens = new IERC20[](1);
loanTokens[0] = IERC20(_tradingPath[0]);

uint256[] memory loanAmounts = new uint256[](1);
loanAmounts[0] = _loanSize;

BALANCER_VAULT.flashLoan(this, loanTokens, loanAmounts, strategyData);
}

/**
* @notice Handles the flash loan callback and executes the arbitrage strategy
* @dev Implements the core arbitrage logic: borrow → swap A → swap B → repay
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external override {
require(msg.sender == address(BALANCER_VAULT), "Unauthorized callback");

ArbStrategy memory strategy = abi.decode(userData, (ArbStrategy));
uint256 loanAmount = amounts[0];

// Execute first leg of arbitrage
executeSwap(
strategy.dexRouters[0],
strategy.tradingPath[0],
loanAmount,
strategy.tradingPath[1],
0, // Accept any output for first swap
strategy.poolFee
);

// Execute second leg of arbitrage
uint256 intermediateBalance = IERC20(strategy.tradingPath[1]).balanceOf(address(this));
executeSwap(
strategy.dexRouters[1],
strategy.tradingPath[1],
intermediateBalance,
strategy.tradingPath[0],
loanAmount, // Minimum output must cover flash loan
strategy.poolFee
);

// Repay flash loan
IERC20(strategy.tradingPath[0]).transfer(address(BALANCER_VAULT), loanAmount);

// Transfer profits to strategist
uint256 profitAmount = IERC20(strategy.tradingPath[0]).balanceOf(address(this));
if (profitAmount > 0) {
IERC20(strategy.tradingPath[0]).transfer(strategist, profitAmount);
}
}

/**
* @notice Executes a single swap on a DEX
* @dev Optimized for exact input swaps with minimum output requirements
*/
function executeSwap(
address _dexRouter,
address _tokenIn,
uint256 _swapAmount,
address _tokenOut,
uint256 _minReturn,
uint24 _poolFee
) internal {
IERC20(_tokenIn).approve(_dexRouter, _swapAmount);

ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({
tokenIn: _tokenIn,
tokenOut: _tokenOut,
fee: _poolFee,
recipient: address(this),
deadline: block.timestamp,
amountIn: _swapAmount,
amountOutMinimum: _minReturn,
sqrtPriceLimitX96: 0
});

ISwapRouter(_dexRouter).exactInputSingle(swapParams);

emit ArbitrageExecuted(_tokenIn, _tokenOut, _swapAmount, _minReturn);
}
}
6 changes: 3 additions & 3 deletions test/unit/ArbitrageTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ pragma solidity 0.8.18;

import {Test, console} from "forge-std/Test.sol";
import {StdUtils} from "forge-std/StdUtils.sol";
import {Arbitrage} from "../../src/Arbitrage.sol";
import {FlashArbitrage} from "../../src/FlashArbitrage.sol";
import {HelperConfig} from "../../script/HelperConfig.s.sol";
import {IERC20} from "@openzeppelin/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";

contract ArbitrageTest is Test {
Arbitrage public arbitrage;
FlashArbitrage public arbitrage;
HelperConfig public helperConfig;
HelperConfig.ForkNetworkConfig public networkConfig;

Expand All @@ -28,7 +28,7 @@ contract ArbitrageTest is Test {
baseSepoliaFork = vm.createSelectFork(ETH_SEPOLIA_RPC_URL);

vm.startPrank(owner);
arbitrage = new Arbitrage();
arbitrage = new FlashArbitrage();
vm.stopPrank();

deal(networkConfig.usdc, owner, 69);
Expand Down
Loading