From f2cf1d9b54787c576123dd61b300b28cb7433a91 Mon Sep 17 00:00:00 2001 From: Hunter King Date: Tue, 9 Jul 2024 21:05:37 -0600 Subject: [PATCH] add leverage calculation tests --- foundry.toml | 2 + src/leverage/ParaswapSellAdapter.sol | 7 +- .../interfaces/IParaswapSellAdapter.sol | 1 + test/e2e/E2ESwapExit.t.sol | 134 ++++++++++++++---- test/e2e/common/CommonTest.t.sol | 10 ++ 5 files changed, 123 insertions(+), 31 deletions(-) diff --git a/foundry.toml b/foundry.toml index 02b212a..eb26578 100644 --- a/foundry.toml +++ b/foundry.toml @@ -14,6 +14,8 @@ src = "src" out = "out" libs = ["lib"] ffi = true +fuzz_runs = 128 +optimizer_runs = 5_000 [rpc_endpoints] mainnet = "${ARB_MAINNET_RPC}" diff --git a/src/leverage/ParaswapSellAdapter.sol b/src/leverage/ParaswapSellAdapter.sol index 4eed1e5..7e8f1d2 100644 --- a/src/leverage/ParaswapSellAdapter.sol +++ b/src/leverage/ParaswapSellAdapter.sol @@ -102,6 +102,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte /// @dev approve address(this) as safeHandler and request to borrow asset on Aave function requestFlashloan( SellParams memory _sellParams, + uint256 _initCollateral, uint256 _collateralLoan, uint256 _minDstAmount, uint256 _safeId, @@ -115,7 +116,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte address(collateralJoinFactory.collateralJoins(_cType)), coinJoin, _safeId, - _collateralLoan - PREMIUM, + _initCollateral + _collateralLoan, _sellParams.sellAmount ); @@ -141,12 +142,15 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte abi.decode(params, (uint256, SellParams, bytes)); emit log_named_uint('RETH BAL AQUIRE LOAN', IERC20Metadata(_sellParams.toToken).balanceOf(address(this))); + emit log_named_uint('OD BAL BEFORE LOCK', IERC20Metadata(_sellParams.fromToken).balanceOf(address(this))); uint256 _beforebalance = IERC20Metadata(_sellParams.fromToken).balanceOf(address(this)); uint256 _sellAmount = _sellParams.sellAmount; _executeFromProxy(_payload); + emit log_named_uint('OD BAL AFTER LOCK', IERC20Metadata(_sellParams.fromToken).balanceOf(address(this))); + // todo add error msg // if (_sellAmount != OD.balanceOf(address(this)) - _beforebalance) revert(); @@ -160,6 +164,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte _minDstAmount ); emit log_named_uint('RETH BAL POST SWAP', IERC20Metadata(_sellParams.toToken).balanceOf(address(this))); + emit log_named_uint('OD BAL AFTER SWAP', IERC20Metadata(_sellParams.fromToken).balanceOf(address(this))); uint256 _payBack = amount + premium; IERC20Metadata(asset).approve(address(POOL), _payBack); diff --git a/src/leverage/interfaces/IParaswapSellAdapter.sol b/src/leverage/interfaces/IParaswapSellAdapter.sol index 70aa514..183af04 100644 --- a/src/leverage/interfaces/IParaswapSellAdapter.sol +++ b/src/leverage/interfaces/IParaswapSellAdapter.sol @@ -54,6 +54,7 @@ interface IParaswapSellAdapter { */ function requestFlashloan( SellParams memory _sellParams, + uint256 _initCollateral, uint256 _collateralLoan, uint256 _minDstAmount, uint256 _safeId, diff --git a/test/e2e/E2ESwapExit.t.sol b/test/e2e/E2ESwapExit.t.sol index fd07037..6d92ed2 100644 --- a/test/e2e/E2ESwapExit.t.sol +++ b/test/e2e/E2ESwapExit.t.sol @@ -5,7 +5,6 @@ import '@script/Registry.s.sol'; import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; import {IVault721} from '@opendollar/interfaces/proxies/IVault721.sol'; import {IDenominatedOracle} from '@opendollar/interfaces/oracles/IDenominatedOracle.sol'; -import {DelayedOracleForTest} from '@opendollar/test/mocks/DelayedOracleForTest.sol'; import {AugustusRegistry} from '@aave-debt-swap/dependencies/paraswap/AugustusRegistry.sol'; import {ParaswapSellAdapter, IParaswapSellAdapter} from 'src/leverage/ParaswapSellAdapter.sol'; import {CommonTest} from 'test/e2e/common/CommonTest.t.sol'; @@ -14,6 +13,7 @@ import {Math} from '@opendollar/libraries/Math.sol'; contract E2ESwapExit is CommonTest { using Math for uint256; + uint256 public constant MAX_SLIPPAGE_PERCENT = 0.3e4; uint256 public constant PREMIUM = 500_000_000_000; uint256 public constant INTEREST_RATE_MODE = 0; uint16 public constant REF_CODE = 0; @@ -32,7 +32,7 @@ contract E2ESwapExit is CommonTest { super.setUp(); rethOracle = IDenominatedOracle(MAINNET_DENOMINATED_RETH_USD_ORACLE); (rethUsdPrice,) = rethOracle.getResultWithValidity(); - setCTypePrice(RETH, rethUsdPrice); + _setCTypePrice(RETH, rethUsdPrice); userProxy = _deployOrFind(USER); label(USER, 'USER-WALLET'); @@ -67,46 +67,120 @@ contract E2ESwapExit is CommonTest { vm.stopPrank(); } - /** - * todo: test with initial deposit of collateral - * initial deposit + loan to overcome the over-collateralization "loss" - * in being able to returning to loan in full + premium - */ - function testRequestFlashloan() public { - assertEq(readCTypePrice(RETH), rethUsdPrice); - uint256 _collateralLoan = 1 ether; - uint256 _sellAmount = (_collateralLoan.wmul(rethUsdPrice) - PREMIUM) * 2 / 3; + /// @dev example of locking collateral at same time of leveraging + function testRequestFlashloan0() public { + uint256 _initCapital = 0.1 ether; + + /// @notice locked 30% collateral independently from capital allocated to leverage (swap loss?) + uint256 _additionalCapital = _initCapital * 30 / 100; + + _testRequestFlashLoan(_initCapital, _additionalCapital); + } + + function testRequestFlashloan1() public { + uint256 _initCapital = 0.5 ether; + + /// @notice locked 35% collateral independently from capital allocated to leverage (swap loss?) + uint256 _additionalCapital = _initCapital * 35 / 100; + + _testRequestFlashLoan(_initCapital, _additionalCapital); + } + + function testRequestFlashloan2() public { + uint256 _initCapital = 1 ether; + + /// @notice locked 45% collateral independently from capital allocated to leverage (swap loss?) + uint256 _additionalCapital = _initCapital * 45 / 100; + + _testRequestFlashLoan(_initCapital, _additionalCapital); + } + + function testRequestFlashloan3() public { + uint256 _initCapital = 5 ether; + + /// @notice locked 55% collateral independently from capital allocated to leverage (swap loss?) + uint256 _additionalCapital = _initCapital * 55 / 100; + + _testRequestFlashLoan(_initCapital, _additionalCapital); + } + + function testRequestFlashloan4() public { + uint256 _initCapital = 10 ether; + + /// @notice locked 55% collateral independently from capital allocated to leverage (swap loss?) + uint256 _additionalCapital = _initCapital * 60 / 100; + + _testRequestFlashLoan(_initCapital, _additionalCapital); + } + + function testMath() public { + // intial reth + uint256 initialCollateral = 0.5 ether; + + // 100 / 1.35% = 74 + uint256 _percentMaxDebt = uint256(10_000) / uint256(135); + emit log_named_uint('_percentMaxDebt ', _percentMaxDebt); + + // 100 - 74 = 26 + uint256 _percentMakeUp = 100 - _percentMaxDebt; + emit log_named_uint('_percentMakeUp ', _percentMakeUp); + + uint256 _maxCollateral = initialCollateral * 100 / _percentMakeUp; + emit log_named_uint('_maxCollateral ', _maxCollateral); + + uint256 _maxTotalLoan = _maxCollateral - initialCollateral; + emit log_named_uint('_maxTotalLoan ', _maxTotalLoan); + + uint256 _maxLoan = _maxTotalLoan - (PREMIUM + MAX_SLIPPAGE_PERCENT); + emit log_named_uint('_maxLoan ', _maxLoan); + } + + // Helper Functions + function _calculateMaxLeverage(uint256 _initialCollateral, uint256 _safetyRatio) public returns (uint256) { + emit log_named_uint('_currentRethPrice ', rethUsdPrice); + + uint256 _percentMaxDebt = uint256(10_000) / _safetyRatio; + uint256 _percentMakeUp = 100 - _percentMaxDebt; + + uint256 _maxCollateral = _initialCollateral * 100 / _percentMakeUp; + emit log_named_uint('_maxCollateral ', _maxCollateral); + + uint256 _maxTotalLoan = _maxCollateral - _initialCollateral; + emit log_named_uint('_maxTotalLoan ', _maxTotalLoan); + + return _maxTotalLoan; + } + + function _testRequestFlashLoan(uint256 _initCapital, uint256 _additionalCapital) internal { + uint256 _deposit = _initCapital + _additionalCapital; + deal(RETH_ADDR, USER, _deposit); + + uint256 _maxLoan = _calculateMaxLeverage(_initCapital, 135); + uint256 _sellAmount = _maxLoan.wmul(rethUsdPrice); emit log_named_uint('DEBT SELL AMOUNT', _sellAmount); (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInputWithAmount(OD_ADDR, RETH_ADDR, _sellAmount); - // todo: lock initial capital in safe - uint256 _initCapital = _collateralLoan / 2; - deal(RETH_ADDR, USER, _initCapital); - vm.prank(userProxy); safeManager.allowSAFE(vaults[userProxy], sellAdapterProxy, true); vm.startPrank(USER); - IERC20Metadata(RETH_ADDR).approve(SELL_ADAPTER, _initCapital); - sellAdapter.deposit(RETH_ADDR, _initCapital); - - // assertEq(IERC20Metadata(RETH_ADDR).balanceOf(SELL_ADAPTER), _initCapital); - // assertEq(IERC20Metadata(OD_ADDR).balanceOf(SELL_ADAPTER), 0); - - sellAdapter.requestFlashloan(_sellParams, _collateralLoan, _dstAmount, vaults[userProxy], RETH); - // assertEq(IERC20Metadata(RETH_ADDR).balanceOf(SELL_ADAPTER), 0); - + IERC20Metadata(RETH_ADDR).approve(SELL_ADAPTER, _deposit); + sellAdapter.deposit(RETH_ADDR, _deposit); + sellAdapter.requestFlashloan(_sellParams, _initCapital, _maxLoan, _dstAmount, vaults[userProxy], RETH); vm.stopPrank(); - } - function setCTypePrice(bytes32 _cType, uint256 _price) public { - DelayedOracleForTest(address(delayedOracle[_cType])).setPriceAndValidity(_price, true); - oracleRelayer.updateCollateralPrice(_cType); + _logFinalValues(_deposit); } - function readCTypePrice(bytes32 _cType) public returns (uint256 _price) { - _price = delayedOracle[_cType].read(); + function _logFinalValues(uint256 _deposit) internal { + (uint256 _c, uint256 _d) = _getSAFE(RETH, userNFV.safeHandler); + emit log_named_uint('ORIGINAL DEBT ', 0); + emit log_named_uint('FINAL DEBT ', _d); + emit log_named_uint('--------------------', 0); + emit log_named_uint('ORIGINAL COLLATERAL ', _deposit); + emit log_named_uint('FINAL COLLATERAL ', _c); + emit log_named_uint('LEVERAGE PERCENTAGE ', _c * 100 / _deposit); } } diff --git a/test/e2e/common/CommonTest.t.sol b/test/e2e/common/CommonTest.t.sol index 0768368..6fafca5 100644 --- a/test/e2e/common/CommonTest.t.sol +++ b/test/e2e/common/CommonTest.t.sol @@ -6,6 +6,7 @@ import {Math} from '@opendollar/libraries/Math.sol'; import {ODProxy} from '@opendollar/contracts/proxies/ODProxy.sol'; import {IVault721} from '@opendollar/interfaces/proxies/IVault721.sol'; import {ISAFEEngine} from '@opendollar/interfaces/ISAFEEngine.sol'; +import {DelayedOracleForTest} from '@opendollar/test/mocks/DelayedOracleForTest.sol'; import {ExitActions} from 'src/leverage/ExitActions.sol'; import {LeverageCalculator} from 'src/leverage/LeverageCalculator.sol'; import {BaseTest} from 'test/e2e/common/BaseTest.t.sol'; @@ -38,6 +39,15 @@ contract CommonTest is Common, BaseTest { token = address(collateral[TKN]); } + function _setCTypePrice(bytes32 _cType, uint256 _price) internal { + DelayedOracleForTest(address(delayedOracle[_cType])).setPriceAndValidity(_price, true); + oracleRelayer.updateCollateralPrice(_cType); + } + + function _readCTypePrice(bytes32 _cType) internal returns (uint256 _price) { + _price = delayedOracle[_cType].read(); + } + function _deployOrFind(address _owner) internal returns (address _proxy) { _proxy = vault721.getProxy(_owner); if (_proxy == address(0)) {