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
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@
[submodule "lib/v3-core"]
path = lib/v3-core
url = https://github.com/uniswap/v3-core
[submodule "lib/aave-v3-core"]
path = lib/aave-v3-core
url = https://github.com/aave/aave-v3-core
[submodule "lib/comet"]
path = lib/comet
url = https://github.com/compound-finance/comet
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ remappings = [
"@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/",
"@aave/v3-core/contracts=lib/aave-v3-core/contracts",
"@compound/contracts=lib/comet/contracts"
]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions lib/aave-v3-core
Submodule aave-v3-core added at b74526
1 change: 1 addition & 0 deletions lib/comet
Submodule comet added at 51c2ad
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 40 additions & 12 deletions script/HelperConfig.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.22;

import {Script, console2} from "forge-std/Script.sol";

Expand All @@ -14,14 +14,14 @@ contract HelperConfig is Script {
//////////////////////////////////////////////////////////////*/

struct NetworkConfig {
address weth; // Mode Network WETH
address usdc; // Mode Network USDC
address weth;
address usdc;
address aaveUsdc; // "a tokens" recieved after supplying liquidity in aave V3
address compoundUsdc;
address uniswapFactory; // Uniswap V3
address uniswapRouter; //Uniswap V3
address uniswapQouter; // Uniswap V3
address forkedUniswapFactory; // Forked Uniswap V3
address forkedUniswapRouter; // Forked Uniswap V3
address forkedUniswapQouter; // Forked Uniswap V3
address aavePool; // Aave V3
}

/*//////////////////////////////////////////////////////////////
Expand All @@ -31,16 +31,44 @@ contract HelperConfig is Script {
NetworkConfig memory BaseSepoliaConfig = NetworkConfig({
weth: 0x4200000000000000000000000000000000000006,
usdc: 0x036CbD53842c5426634e7929541eC2318f3dCF7e,
aaveUsdc: 0xfE45Bf4dEF7223Ab1Bf83cA17a4462Ef1647F7FF,
compoundUsdc: 0x571621Ce60Cebb0c1D442B5afb38B1663C6Bf017,
uniswapFactory: 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24,
uniswapRouter: 0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4,
uniswapQouter: 0xC5290058841028F1614F3A6F0F5816cAd0df5E27,
forkedUniswapFactory: 0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865,
forkedUniswapRouter: 0x1b81D678ffb9C0263b24A97847620C99d213eB14,
forkedUniswapQouter: 0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997
aavePool: 0xbE781D7Bdf469f3d94a62Cdcc407aCe106AEcA74
});
return BaseSepoliaConfig;
}

function getAvaxFujiConfig() public pure returns (NetworkConfig memory) {
NetworkConfig memory AvaxFujiConfig = NetworkConfig({
weth: address(0),
usdc: 0x5425890298aed601595a70AB815c96711a31Bc65,
aaveUsdc: 0x9CFcc1B289E59FBe1E769f020C77315DF8473760,
compoundUsdc: address(0),
uniswapFactory: address(0),
uniswapRouter: address(0),
uniswapQouter: address(0),
aavePool: 0x8B9b2AF4afB389b4a70A474dfD4AdCD4a302bb40
});
return AvaxFujiConfig;
}

function getETHSepoliaConfig() public pure returns (NetworkConfig memory) {
NetworkConfig memory ETHSepoliaConfig = NetworkConfig({
weth: 0xC558DBdd856501FCd9aaF1E62eae57A9F0629a3c,
usdc: 0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8,
aaveUsdc: address(0),
compoundUsdc: 0xE3E0106227181958aBfbA960C13d0Fe52c733265,
uniswapFactory: address(0),
uniswapRouter: address(0),
uniswapQouter: address(0),
aavePool: 0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951
});
return ETHSepoliaConfig;
}

/*//////////////////////////////////////////////////////////////
LOCAL CONFIG
//////////////////////////////////////////////////////////////*/
Expand All @@ -49,12 +77,12 @@ contract HelperConfig is Script {
NetworkConfig memory AnvilConfig = NetworkConfig({
weth: address(0),
usdc: address(1),
aaveUsdc: address(0),
compoundUsdc: address(0),
uniswapFactory: address(2),
uniswapRouter: address(3),
uniswapQouter: address(6),
forkedUniswapFactory: address(4),
forkedUniswapRouter: address(5),
forkedUniswapQouter: address(6)
aavePool: address(7)
});
return AnvilConfig;
}
Expand Down
46 changes: 31 additions & 15 deletions src/Arbitrage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.so
*/
contract Arbitrage is IFlashLoanRecipient {
/// @notice Balancer V2 Vault address (Ethereum mainnet)
IVault private constant vault = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IVault private constant vault =
IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);

/// @notice Owner address to receive arbitrage profits
address public owner;
Expand All @@ -43,7 +44,12 @@ contract Arbitrage is IFlashLoanRecipient {
* @param amountIn Amount of tokenIn swapped
* @param minAmountOut Minimum expected amount of tokenOut (slippage protection)
*/
event TokensSwapped(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut);
event TokensSwapped(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut
);

/// @dev Sets the contract deployer as the owner
constructor() {
Expand All @@ -67,8 +73,14 @@ contract Arbitrage is IFlashLoanRecipient {
uint256 _flashAmount
) external {
// Encode trade parameters for flash loan callback
bytes memory data =
abi.encode(Trade({routerPath: _routerPath, quoterPath: _quoterPath, tokenPath: _tokenPath, fee: _fee}));
bytes memory data = abi.encode(
Trade({
routerPath: _routerPath,
quoterPath: _quoterPath,
tokenPath: _tokenPath,
fee: _fee
})
);

// Configure flash loan parameters
IERC20[] memory tokens = new IERC20[](1);
Expand All @@ -95,7 +107,10 @@ contract Arbitrage is IFlashLoanRecipient {
uint256[] memory feeAmounts,
bytes memory userData
) external override {
require(msg.sender == address(vault), "Unauthorized: Only Balancer Vault");
require(
msg.sender == address(vault),
"Unauthorized: Only Balancer Vault"
);

// Decode trade parameters from userData
Trade memory trade = abi.decode(userData, (Trade));
Expand Down Expand Up @@ -153,16 +168,17 @@ contract Arbitrage is IFlashLoanRecipient {
IERC20(_tokenIn).approve(_router, _amountIn);

// Configure single-hop swap parameters
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: _tokenIn,
tokenOut: _tokenOut,
fee: _fee,
recipient: address(this), // Send output tokens to this contract
deadline: block.timestamp, // Expire after current block
amountIn: _amountIn,
amountOutMinimum: _amountOut, // Minimum output for successful swap
sqrtPriceLimitX96: 0 // No price limit (accept any slippage)
});
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: _tokenIn,
tokenOut: _tokenOut,
fee: _fee,
recipient: address(this), // Send output tokens to this contract
deadline: block.timestamp, // Expire after current block
amountIn: _amountIn,
amountOutMinimum: _amountOut, // Minimum output for successful swap
sqrtPriceLimitX96: 0 // No price limit (accept any slippage)
});

// Execute swap on specified router
ISwapRouter(_router).exactInputSingle(params);
Expand Down
132 changes: 132 additions & 0 deletions src/LiquidityManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import {IPool} from "@aave/v3-core/contracts/interfaces/IPool.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {CometMainInterface} from "./interfaces/IComet.sol";
import {CometExtInterface} from "./interfaces/ICometExt.sol";

/**
* @title LiquidityManager
* @author Cento-AI
* @notice Contract that integrates with aave and compound protocols to supply and withdraw liquidity.
* @dev For compound finance, only USDC investment is available for now.
*/
contract LiquidityManager {
IPool public aavePool;
CometMainInterface public compoundUsdc;

event LiquiditySupplied(
string protocol,
address asset,
uint256 amount,
address user
);

/**
*
* @param _aavePool Aave V3 pool address.
* @param _compoundUsdc Compound USDC address.(Currently only USDC supported for compound).
*/
constructor(address _aavePool, address _compoundUsdc) {
aavePool = IPool(_aavePool);
compoundUsdc = CometMainInterface(_compoundUsdc);
}

function supplyLiquidityOnAave(address _asset, uint256 _amount) external {
require(
IERC20(_asset).allowance(msg.sender, address(this)) >= _amount,
"Insufficient allowance"
);
bool approvedUser = IERC20(_asset).transferFrom(
msg.sender,
address(this),
_amount
);
require(approvedUser, "Transfer of asset into this contract failed");
bool approvedAave = IERC20(_asset).approve(address(aavePool), _amount);
require(approvedAave, "Approval of asset into Aave pool failed");
aavePool.supply(_asset, _amount, msg.sender, 0);
emit LiquiditySupplied("Aave", _asset, _amount, msg.sender);
}

function supplyLiquidityOnCompound(
address _asset,
uint256 _amount
) external {
require(
IERC20(_asset).allowance(msg.sender, address(this)) >= _amount,
"Insufficient allowance"
);
bool approvedUser = IERC20(_asset).transferFrom(
msg.sender,
address(this),
_amount
);
require(approvedUser, "Transfer of asset into this contract failed");
bool approvedCompound = IERC20(_asset).approve(
address(compoundUsdc),
_amount
);
require(approvedCompound, "Approval of asset into Compound failed");
compoundUsdc.supplyTo(msg.sender, _asset, _amount);
emit LiquiditySupplied("Compound", _asset, _amount, msg.sender);
}

function withdrawLiquidityFromAave(
address _asset,
address _aaveAsset,
uint256 _amount
) external returns (uint256 amountWithdrawn) {
(uint256 collateral, , , , , ) = getAaveLiquidityStatus(msg.sender);
require(collateral >= _amount, "Cannot withdraw more than borrowed");
bool success = IERC20(_aaveAsset).transferFrom(
msg.sender,
address(this),
_amount
);
require(success, "Transfer of aave asset into this contract failed");
amountWithdrawn = aavePool.withdraw(_asset, _amount, msg.sender);
}

function withdrawLiquidityFromCompound(
address _asset,
uint256 _amount
) external {
uint256 collateral = getCompoundLiquidityStatus(msg.sender);
require(collateral >= _amount, "Cannot withdraw more than borrowed");
compoundUsdc.withdrawFrom(msg.sender, address(this), _asset, _amount);
}

/**
* @notice Returns the user account data across all the reserves
* @param _user The address of the user
* @return totalCollateralBase The total collateral of the user in the base currency used by the price feed
* @return totalDebtBase The total debt of the user in the base currency used by the price feed
* @return availableBorrowsBase The borrowing power left of the user in the base currency used by the price feed
* @return currentLiquidationThreshold The liquidation threshold of the user
* @return ltv The loan to value of The user
* @return healthFactor The current health factor of the user
*/
function getAaveLiquidityStatus(
address _user
)
public
view
returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
)
{
return aavePool.getUserAccountData(_user);
}

function getCompoundLiquidityStatus(
address _user
) public view returns (uint256 balance) {
balance = compoundUsdc.balanceOf(_user);
}
}
Loading
Loading