diff --git a/.gitmodules b/.gitmodules index bd8a6ae..d3b182b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/foundry.toml b/foundry.toml index 848e6cd..2751493 100644 --- a/foundry.toml +++ b/foundry.toml @@ -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 diff --git a/lib/aave-v3-core b/lib/aave-v3-core new file mode 160000 index 0000000..b74526a --- /dev/null +++ b/lib/aave-v3-core @@ -0,0 +1 @@ +Subproject commit b74526a7bc67a3a117a1963fc871b3eb8cea8435 diff --git a/lib/comet b/lib/comet new file mode 160000 index 0000000..51c2ad5 --- /dev/null +++ b/lib/comet @@ -0,0 +1 @@ +Subproject commit 51c2ad5e02a74b6a824cf3f864ffbb7ec498d6c8 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..55a71ae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "contracts", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index 7272a5a..a9ddee4 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -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"; @@ -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 } /*////////////////////////////////////////////////////////////// @@ -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 //////////////////////////////////////////////////////////////*/ @@ -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; } diff --git a/src/Arbitrage.sol b/src/Arbitrage.sol index 96ce85e..4f07e5a 100644 --- a/src/Arbitrage.sol +++ b/src/Arbitrage.sol @@ -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; @@ -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() { @@ -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); @@ -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)); @@ -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); diff --git a/src/LiquidityManager.sol b/src/LiquidityManager.sol new file mode 100644 index 0000000..72fd344 --- /dev/null +++ b/src/LiquidityManager.sol @@ -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); + } +} diff --git a/src/interfaces/IComet.sol b/src/interfaces/IComet.sol new file mode 100644 index 0000000..3ce6435 --- /dev/null +++ b/src/interfaces/IComet.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.22; + +/** + * @title Compound's Comet Main Interface (without Ext) + * @notice An efficient monolithic money market protocol + * @author Compound + */ +abstract contract CometMainInterface { + error Absurd(); + error AlreadyInitialized(); + error BadAsset(); + error BadDecimals(); + error BadDiscount(); + error BadMinimum(); + error BadPrice(); + error BorrowTooSmall(); + error BorrowCFTooLarge(); + error InsufficientReserves(); + error LiquidateCFTooLarge(); + error NoSelfTransfer(); + error NotCollateralized(); + error NotForSale(); + error NotLiquidatable(); + error Paused(); + error ReentrantCallBlocked(); + error SupplyCapExceeded(); + error TimestampTooLarge(); + error TooManyAssets(); + error TooMuchSlippage(); + error TransferInFailed(); + error TransferOutFailed(); + error Unauthorized(); + + event Supply(address indexed from, address indexed dst, uint amount); + event Transfer(address indexed from, address indexed to, uint amount); + event Withdraw(address indexed src, address indexed to, uint amount); + + event SupplyCollateral( + address indexed from, + address indexed dst, + address indexed asset, + uint amount + ); + event TransferCollateral( + address indexed from, + address indexed to, + address indexed asset, + uint amount + ); + event WithdrawCollateral( + address indexed src, + address indexed to, + address indexed asset, + uint amount + ); + + /// @notice Event emitted when a borrow position is absorbed by the protocol + event AbsorbDebt( + address indexed absorber, + address indexed borrower, + uint basePaidOut, + uint usdValue + ); + + /// @notice Event emitted when a user's collateral is absorbed by the protocol + event AbsorbCollateral( + address indexed absorber, + address indexed borrower, + address indexed asset, + uint collateralAbsorbed, + uint usdValue + ); + + /// @notice Event emitted when a collateral asset is purchased from the protocol + event BuyCollateral( + address indexed buyer, + address indexed asset, + uint baseAmount, + uint collateralAmount + ); + + /// @notice Event emitted when an action is paused/unpaused + event PauseAction( + bool supplyPaused, + bool transferPaused, + bool withdrawPaused, + bool absorbPaused, + bool buyPaused + ); + + /// @notice Event emitted when reserves are withdrawn by the governor + event WithdrawReserves(address indexed to, uint amount); + + function supply(address asset, uint amount) external virtual; + + function supplyTo(address dst, address asset, uint amount) external virtual; + + function supplyFrom( + address from, + address dst, + address asset, + uint amount + ) external virtual; + + function transfer(address dst, uint amount) external virtual returns (bool); + + function transferFrom( + address src, + address dst, + uint amount + ) external virtual returns (bool); + + function transferAsset( + address dst, + address asset, + uint amount + ) external virtual; + + function transferAssetFrom( + address src, + address dst, + address asset, + uint amount + ) external virtual; + + function withdraw(address asset, uint amount) external virtual; + + function withdrawTo( + address to, + address asset, + uint amount + ) external virtual; + + function withdrawFrom( + address src, + address to, + address asset, + uint amount + ) external virtual; + + function approveThis( + address manager, + address asset, + uint amount + ) external virtual; + + function withdrawReserves(address to, uint amount) external virtual; + + function absorb( + address absorber, + address[] calldata accounts + ) external virtual; + + function buyCollateral( + address asset, + uint minAmount, + uint baseAmount, + address recipient + ) external virtual; + + function quoteCollateral( + address asset, + uint baseAmount + ) public view virtual returns (uint); + + // function getAssetInfo( + // uint8 i + // ) public view virtual returns (AssetInfo memory); + + // function getAssetInfoByAddress( + // address asset + // ) public view virtual returns (AssetInfo memory); + + function getCollateralReserves( + address asset + ) public view virtual returns (uint); + + function getReserves() public view virtual returns (int); + + function getPrice(address priceFeed) public view virtual returns (uint); + + function isBorrowCollateralized( + address account + ) public view virtual returns (bool); + + function isLiquidatable(address account) public view virtual returns (bool); + + function totalSupply() external view virtual returns (uint256); + + function totalBorrow() external view virtual returns (uint256); + + function balanceOf(address owner) public view virtual returns (uint256); + + function borrowBalanceOf( + address account + ) public view virtual returns (uint256); + + function pause( + bool supplyPaused, + bool transferPaused, + bool withdrawPaused, + bool absorbPaused, + bool buyPaused + ) external virtual; + + function isSupplyPaused() public view virtual returns (bool); + + function isTransferPaused() public view virtual returns (bool); + + function isWithdrawPaused() public view virtual returns (bool); + + function isAbsorbPaused() public view virtual returns (bool); + + function isBuyPaused() public view virtual returns (bool); + + function accrueAccount(address account) external virtual; + + function getSupplyRate( + uint utilization + ) public view virtual returns (uint64); + + function getBorrowRate( + uint utilization + ) public view virtual returns (uint64); + + function getUtilization() public view virtual returns (uint); + + function governor() external view virtual returns (address); + + function pauseGuardian() external view virtual returns (address); + + function baseToken() external view virtual returns (address); + + function baseTokenPriceFeed() external view virtual returns (address); + + function extensionDelegate() external view virtual returns (address); + + /// @dev uint64 + function supplyKink() external view virtual returns (uint); + + /// @dev uint64 + function supplyPerSecondInterestRateSlopeLow() + external + view + virtual + returns (uint); + + /// @dev uint64 + function supplyPerSecondInterestRateSlopeHigh() + external + view + virtual + returns (uint); + + /// @dev uint64 + function supplyPerSecondInterestRateBase() + external + view + virtual + returns (uint); + + /// @dev uint64 + function borrowKink() external view virtual returns (uint); + + /// @dev uint64 + function borrowPerSecondInterestRateSlopeLow() + external + view + virtual + returns (uint); + + /// @dev uint64 + function borrowPerSecondInterestRateSlopeHigh() + external + view + virtual + returns (uint); + + /// @dev uint64 + function borrowPerSecondInterestRateBase() + external + view + virtual + returns (uint); + + /// @dev uint64 + function storeFrontPriceFactor() external view virtual returns (uint); + + /// @dev uint64 + function baseScale() external view virtual returns (uint); + + /// @dev uint64 + function trackingIndexScale() external view virtual returns (uint); + + /// @dev uint64 + function baseTrackingSupplySpeed() external view virtual returns (uint); + + /// @dev uint64 + function baseTrackingBorrowSpeed() external view virtual returns (uint); + + /// @dev uint104 + function baseMinForRewards() external view virtual returns (uint); + + /// @dev uint104 + function baseBorrowMin() external view virtual returns (uint); + + /// @dev uint104 + function targetReserves() external view virtual returns (uint); + + function numAssets() external view virtual returns (uint8); + + function decimals() external view virtual returns (uint8); + + function initializeStorage() external virtual; +} diff --git a/src/interfaces/ICometExt.sol b/src/interfaces/ICometExt.sol new file mode 100644 index 0000000..ab192e4 --- /dev/null +++ b/src/interfaces/ICometExt.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.22; + +/** + * @title Compound's Comet Ext Interface + * @notice An efficient monolithic money market protocol + * @author Compound + */ +abstract contract CometExtInterface { + error BadAmount(); + error BadNonce(); + error BadSignatory(); + error InvalidValueS(); + error InvalidValueV(); + error SignatureExpired(); + + function allow(address manager, bool isAllowed) external virtual; + + function allowBySig( + address owner, + address manager, + bool isAllowed, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external virtual; + + function collateralBalanceOf( + address account, + address asset + ) external view virtual returns (uint128); + + function baseTrackingAccrued( + address account + ) external view virtual returns (uint64); + + function baseAccrualScale() external view virtual returns (uint64); + + function baseIndexScale() external view virtual returns (uint64); + + function factorScale() external view virtual returns (uint64); + + function priceScale() external view virtual returns (uint64); + + function maxAssets() external view virtual returns (uint8); + + // function totalsBasic() external view virtual returns (TotalsBasic memory); + + function version() external view virtual returns (string memory); + + /** + * ===== ERC20 interfaces ===== + * Does not include the following functions/events, which are defined in `CometMainInterface` instead: + * - function decimals() virtual external view returns (uint8) + * - function totalSupply() virtual external view returns (uint256) + * - function transfer(address dst, uint amount) virtual external returns (bool) + * - function transferFrom(address src, address dst, uint amount) virtual external returns (bool) + * - function balanceOf(address owner) virtual external view returns (uint256) + * - event Transfer(address indexed from, address indexed to, uint256 amount) + */ + function name() external view virtual returns (string memory); + + function symbol() external view virtual returns (string memory); + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve( + address spender, + uint256 amount + ) external virtual returns (bool); + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance( + address owner, + address spender + ) external view virtual returns (uint256); + + event Approval( + address indexed owner, + address indexed spender, + uint256 amount + ); +} diff --git a/test/forked-uint/LiquidityManagerTest.sol b/test/forked-uint/LiquidityManagerTest.sol new file mode 100644 index 0000000..06dba3a --- /dev/null +++ b/test/forked-uint/LiquidityManagerTest.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Test, console} from "forge-std/Test.sol"; +import {StdUtils} from "forge-std/StdUtils.sol"; +import {LiquidityManager} from "../../src/LiquidityManager.sol"; +import {CometMainInterface} from "../../src/interfaces/IComet.sol"; +import {CometExtInterface} from "../../src/interfaces/ICometExt.sol"; +import {HelperConfig} from "../../script/HelperConfig.s.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract LiquidityManagerTest is Test { + LiquidityManager public supplyLiquidity; + HelperConfig public helperConfig; + HelperConfig.NetworkConfig public networkConfig; + address owner = address(1); + uint256 sepoliaFork; + uint256 fork; + string ETH_SEPOLIA_RPC_URL = vm.envString("ETH_SEPOLIA_RPC_URL"); + string AVAX_FUJI_RPC_URL = vm.envString("AVAX_FUJI_RPC_URL"); + string BASE_SEPOLIA_RPC_URL_2 = vm.envString("BASE_SEPOLIA_RPC_URL_2"); + + function setUp() public { + helperConfig = new HelperConfig(); + networkConfig = helperConfig.getBaseSepoliaConfig(); + fork = vm.createSelectFork(BASE_SEPOLIA_RPC_URL_2); + vm.startPrank(owner); + supplyLiquidity = new LiquidityManager( + address(networkConfig.aavePool), + address(networkConfig.compoundUsdc) + ); + vm.stopPrank(); + deal(networkConfig.usdc, owner, 1000000000000); + } + + function testSupplyUSDCOnAave() public { + vm.startPrank(owner); + IERC20(networkConfig.usdc).approve(address(supplyLiquidity), 69); + supplyLiquidity.supplyLiquidityOnAave(networkConfig.usdc, 69); + (uint256 totalCollateralBase, , , , , ) = supplyLiquidity + .getAaveLiquidityStatus(owner); + assertGt(totalCollateralBase, 0); + assertEq(IERC20(networkConfig.aaveUsdc).balanceOf(address(owner)), 69); + vm.stopPrank(); + } + + function testWithdrawUSDCFromAave() public { + vm.startPrank(owner); + IERC20(networkConfig.usdc).approve(address(supplyLiquidity), 69); + supplyLiquidity.supplyLiquidityOnAave(networkConfig.usdc, 69); + IERC20(networkConfig.aaveUsdc).approve(address(supplyLiquidity), 69); + supplyLiquidity.withdrawLiquidityFromAave( + networkConfig.usdc, + networkConfig.aaveUsdc, + 69 + ); + (uint256 totalCollateralBase, , , , , ) = supplyLiquidity + .getAaveLiquidityStatus(owner); + assertEq(totalCollateralBase, 0); + vm.stopPrank(); + } + + function testSupplyUSDCOnCompound() public { + vm.startPrank(owner); + IERC20(networkConfig.usdc).approve(address(supplyLiquidity), 69000000); + supplyLiquidity.supplyLiquidityOnCompound(networkConfig.usdc, 69000000); + assertGt(supplyLiquidity.getCompoundLiquidityStatus(owner), 0); + vm.stopPrank(); + } + + function testWithdrawUSDCFromCompound() public { + vm.startPrank(owner); + IERC20(networkConfig.usdc).approve(address(supplyLiquidity), 69000000); + supplyLiquidity.supplyLiquidityOnCompound(networkConfig.usdc, 69000000); + CometExtInterface(networkConfig.compoundUsdc).allow( + address(supplyLiquidity), + true + ); + supplyLiquidity.withdrawLiquidityFromCompound(networkConfig.usdc, 100); + assertLt(supplyLiquidity.getCompoundLiquidityStatus(owner), 69000000); + } +}