diff --git a/README.md b/README.md index e6b42e5..8557c02 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,17 @@ Proxy action contracts for Open Dollar - https://book.getfoundry.sh/ - https://contracts.opendollar.com - https://docs.opendollar.com + +## ParaswapSellAdapter Docs + +### get max loan and leveraged amount of debt: + +IParaswapSellAdapter.getLeveragedDebt(bytes32 _cType, uint256 _initCapital) + +### get loan and leveraged amount of debt with percentage buffer: + +IParaswapSellAdapter.getLeveragedDebt(bytes32 _cType, uint256 _initCapital, uint256 _percentageBuffer) + +### execute leverage flashloan + +IParaswapSellAdapter.requestFlashloan(SellParams memory _sellParams, uint256 _initCollateral, uint256 _collateralLoan, uint256 _minDstAmount, uint256 _safeId, bytes32 _cType) \ No newline at end of file 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/package.json b/package.json index 1b76053..374e6e7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test:coverage": "forge coverage --report lcov && lcov --ignore-errors unused --remove lcov.info 'node_modules/*' 'script/*' 'test/*' 'src/contracts/for-test/*' 'src/libraries/*' -o lcov.info.pruned && mv lcov.info.pruned lcov.info && genhtml -o coverage-report lcov.info" }, "dependencies": { - "@opendollar/contracts": "0.0.0-984c17c2", + "@opendollar/contracts": "0.0.0-00b33fa2", "@openzeppelin-contracts-3.4.2-solc-0.7": "yarn:@openzeppelin/contracts@^3.4.2", "@openzeppelin/contracts": "4.9.6", "@paraswap/sdk": "^6.7.0", diff --git a/script/getSwapRoute.js b/script/getSwapTransaction.js similarity index 82% rename from script/getSwapRoute.js rename to script/getSwapTransaction.js index 148db75..0256437 100644 --- a/script/getSwapRoute.js +++ b/script/getSwapTransaction.js @@ -12,7 +12,7 @@ const TO_DECIMALS = args[3]; const SELL_AMOUNT = args[4]; const CALLER = args[5]; -async function getSwapRoute(_fromToken, _fromDecimals, _toToken, _toDecimals, _sellAmount, _caller) { +async function getSwapTransaction(_fromToken, _fromDecimals, _toToken, _toDecimals, _sellAmount, _caller) { const priceRoute = await paraSwapMin.swap.getRate({ srcToken: _fromToken, srcDecimals: _fromDecimals, @@ -38,4 +38,4 @@ async function getSwapRoute(_fromToken, _fromDecimals, _toToken, _toDecimals, _s process.stdout.write(txParams.data); } -getSwapRoute(FROM_TOKEN, FROM_DECIMALS, TO_TOKEN, TO_DECIMALS, SELL_AMOUNT, CALLER); \ No newline at end of file +getSwapTransaction(FROM_TOKEN, FROM_DECIMALS, TO_TOKEN, TO_DECIMALS, SELL_AMOUNT, CALLER); \ No newline at end of file diff --git a/src/leverage/ExitActions.sol b/src/leverage/ExitActions.sol index 801168d..2e7228c 100644 --- a/src/leverage/ExitActions.sol +++ b/src/leverage/ExitActions.sol @@ -133,7 +133,6 @@ contract ExitActions is BasicActions { ODSafeManager.SAFEData memory _safeInfo = ODSafeManager(_manager).safeData(_safeId); // takes token amount from user's wallet and joins into the safeEngine _joinCollateral(_collateralJoin, _safeInfo.safeHandler, _collateralAmount); - int256 _deltaDebt = _getGeneratedDeltaDebt(_safeEngine, _safeInfo.collateralType, _safeInfo.safeHandler, _deltaWad); // locks token amount into the SAFE and generates debt _modifySAFECollateralization(_manager, _safeId, _collateralAmount.toInt(), _deltaDebt, false); diff --git a/src/leverage/LeverageCalculator.sol b/src/leverage/LeverageCalculator.sol deleted file mode 100644 index 5261e79..0000000 --- a/src/leverage/LeverageCalculator.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.20; - -import {ISAFEEngine} from '@opendollar/interfaces/ISAFEEngine.sol'; -import {IVault721} from '@opendollar/interfaces/proxies/IVault721.sol'; -import {IODSafeManager} from '@opendollar/interfaces/proxies/IODSafeManager.sol'; -import {Math, RAY} from '@opendollar/libraries/Math.sol'; - -contract LeverageCalculator { - using Math for uint256; - - IVault721 public immutable VAULT721; - ISAFEEngine public immutable SAFEENGINE; - - constructor(address _vault721) { - VAULT721 = IVault721(_vault721); - SAFEENGINE = ISAFEEngine(IODSafeManager(VAULT721.safeManager()).safeEngine()); - } - - /// @dev calculate max single-swap leverage based on initial locked collateral - function calculateSingleLeverage(uint256 _safeId) public view returns (uint256 _leverage) { - (bytes32 _cType, address _safeHandler) = getNFVIds(_safeId); - (uint256 _collateral, uint256 _debt) = getNFVLockedAndDebt(_cType, _safeHandler); - (uint256 _accumulatedRate, uint256 _safetyPrice) = getCData(_cType); - - uint256 _maxSafetyDebt = _collateral.wmul(_safetyPrice).wdiv(_accumulatedRate); - - if (_maxSafetyDebt > _debt) { - _leverage = _maxSafetyDebt - _debt; - } - } - - /// @dev calculate max loop-swap leverage based on initial locked collateral - function calculateMultipleLeverage(uint256 _safeId) external view returns (uint256 _leverage) { - uint256 _accumulator; - uint256 _debtIterator; - - _debtIterator = calculateSingleLeverage(_safeId); - - while (_debtIterator > 200 ether) { - _accumulator += _debtIterator; - // TODO: recalculate collateral or execute leverage swap - _debtIterator = calculateSingleLeverage(_safeId); - } - - return _leverage; - } - - /// @dev calculate max flashloan leverage based on initial locked collateral - function calculateFlashLeverage(uint256 _safeId) external pure returns (uint256 _leverage) { - // TODO: calculate max leverage - return _safeId; - } - - /// @return _internalDebt internal account of COIN for an account (internal) - function getCoinBalance(address _proxy) public view returns (uint256 _internalDebt) { - _internalDebt = SAFEENGINE.coinBalance(_proxy) / RAY; - } - - /// @dev get cType and safe handler of NFV - function getNFVIds(uint256 _safeId) public view returns (bytes32 _cType, address _safeHandler) { - IVault721.NFVState memory _nftState = VAULT721.getNfvState(_safeId); - _cType = _nftState.cType; - _safeHandler = _nftState.safeHandler; - } - - /// @dev get locked collateral and generated debt - function getNFVLockedAndDebt( - bytes32 _cType, - address _safeHandler - ) public view returns (uint256 _collateral, uint256 _debt) { - ISAFEEngine.SAFE memory _safeData = SAFEENGINE.safes(_cType, _safeHandler); - _collateral = _safeData.lockedCollateral; - _debt = _safeData.generatedDebt; - } - - /// @dev get accumulated rate and safety price for a cType - function getCData(bytes32 _cType) public view returns (uint256 _accumulatedRate, uint256 _safetyPrice) { - ISAFEEngine.SAFEEngineCollateralData memory _safeEngCData = SAFEENGINE.cData(_cType); - _accumulatedRate = _safeEngCData.accumulatedRate; - _safetyPrice = _safeEngCData.safetyPrice; - } -} diff --git a/src/leverage/ParaswapSellAdapter.sol b/src/leverage/ParaswapSellAdapter.sol index 4eed1e5..a7ea24b 100644 --- a/src/leverage/ParaswapSellAdapter.sol +++ b/src/leverage/ParaswapSellAdapter.sol @@ -1,121 +1,108 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; -import 'forge-std/Test.sol'; import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; import {FlashLoanSimpleReceiverBase} from '@aave-core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol'; import {IPoolAddressesProvider} from '@aave-core-v3/contracts/interfaces/IPoolAddressesProvider.sol'; -// import {PercentageMath} from '@aave-core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; +import {PercentageMath} from '@aave-core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; import {IParaSwapAugustusRegistry} from '@aave-debt-swap/dependencies/paraswap/IParaSwapAugustusRegistry.sol'; import {ODProxy} from '@opendollar/contracts/proxies/ODProxy.sol'; -import {IODSafeManager} from '@opendollar/interfaces/proxies/IODSafeManager.sol'; -import {ICollateralJoinFactory} from '@opendollar/interfaces/factories/ICollateralJoinFactory.sol'; -import {IVault721} from '@opendollar/interfaces/proxies/IVault721.sol'; -import {ISAFEEngine} from '@opendollar/interfaces/ISAFEEngine.sol'; -import {IParaswapSellAdapter} from 'src/leverage/interfaces/IParaswapSellAdapter.sol'; +import {Modifiable} from '@opendollar/contracts/utils/Modifiable.sol'; +import {Authorizable} from '@opendollar/contracts/utils/Authorizable.sol'; +import { + ISAFEEngine, + IODSafeManager, + ICollateralJoinFactory, + ICollateralJoin, + IOracleRelayer, + IVault721 +} from '@opendollar/libraries/OpenDollarV1Arbitrum.sol'; +import {Assertions} from '@opendollar/libraries/Assertions.sol'; +import {Encoding} from '@opendollar/libraries/Encoding.sol'; +import {Math} from '@opendollar/libraries/Math.sol'; +import {IParaswapSellAdapter, InitSellAdapter} from 'src/leverage/interfaces/IParaswapSellAdapter.sol'; import {IParaswapAugustus} from 'src/leverage/interfaces/IParaswapAugustus.sol'; -import {ExitActions} from 'src/leverage/ExitActions.sol'; - -/** - * TODO: - * - add access control - * - add modifiable contract for var updates - * - add withdraw function - * - enforce max slippage rate - * - simplify the constructor with a struct - * - remove Test inheritance - */ -contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapter, Test { - // using PercentageMath for uint256; - // uint256 public constant MAX_SLIPPAGE_PERCENT = 0.3e4; // 30.00% - uint256 public constant PREMIUM = 500_000_000_000; +import {IExitActions} from 'src/leverage/interfaces/IExitActions.sol'; + +contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapter, Modifiable { + using PercentageMath for uint256; + using Math for uint256; + using Assertions for address; + using Encoding for bytes; + + uint256 public constant MAX_SLIPPAGE_PERCENT = 0.03e4; // 3% IParaSwapAugustusRegistry public immutable AUGUSTUS_REGISTRY; ODProxy public immutable PS_ADAPTER_ODPROXY; - IERC20Metadata public immutable OPEN_DOLLAR; IParaswapAugustus public augustus; IODSafeManager public safeManager; + IOracleRelayer public oracleRelayer; ICollateralJoinFactory public collateralJoinFactory; - // todo make interface for this - ExitActions public exitActions; + IExitActions public exitActions; address public coinJoin; - mapping(address => mapping(address => uint256)) internal _deposits; - - /** - * @param _augustusRegistry address of Paraswap AugustusRegistry - * @param _augustusSwapper address of Paraswap AugustusSwapper - * @param _poolProvider address of Aave PoolAddressProvider - * @param _vault721 address of OpenDollar Vault721 - * @param _exitActions address of OpenDollar ExitActions - * @param _collateralJoinFactory address of OpenDollar CollateralJoinFactory - * @param _coinJoin address of OpenDollar CoinJoin - */ - constructor( - address _systemCoin, - address _augustusRegistry, - address _augustusSwapper, - address _poolProvider, - address _vault721, - address _exitActions, - address _collateralJoinFactory, - address _coinJoin - ) FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_poolProvider)) { - OPEN_DOLLAR = IERC20Metadata(_systemCoin); - AUGUSTUS_REGISTRY = IParaSwapAugustusRegistry(_augustusRegistry); - augustus = IParaswapAugustus(_augustusSwapper); - IVault721 _v721 = IVault721(_vault721); + constructor(InitSellAdapter memory _initSellAdapter) + FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_initSellAdapter.poolProvider)) + Authorizable(msg.sender) + { + AUGUSTUS_REGISTRY = IParaSwapAugustusRegistry(_initSellAdapter.augustusRegistry); + augustus = IParaswapAugustus(_initSellAdapter.augustusSwapper); + IVault721 _v721 = IVault721(_initSellAdapter.vault721); safeManager = IODSafeManager(_v721.safeManager()); - exitActions = ExitActions(_exitActions); - collateralJoinFactory = ICollateralJoinFactory(_collateralJoinFactory); - coinJoin = _coinJoin; + oracleRelayer = IOracleRelayer(_initSellAdapter.oracleRelayer); + exitActions = IExitActions(_initSellAdapter.exitActions); + collateralJoinFactory = ICollateralJoinFactory(_initSellAdapter.collateralJoinFactory); + coinJoin = _initSellAdapter.coinJoin; PS_ADAPTER_ODPROXY = ODProxy(_v721.build(address(this))); } - /// @dev deposit asset for msg.sender - function deposit(address _asset, uint256 _amount) external { - _deposit(msg.sender, _asset, _amount); + /// @dev get accumulated rate and safety price for a cType + function getCData(bytes32 _cType) external view returns (uint256 _accumulatedRate, uint256 _safetyPrice) { + (_accumulatedRate, _safetyPrice) = _getCData(_cType); } - /// @dev deposit asset for account - function deposit(address _onBehalfOf, address _asset, uint256 _amount) external { - _deposit(_onBehalfOf, _asset, _amount); + /// @dev get max collateral loan and max leveraged debt + function getLeveragedDebt( + bytes32 _cType, + uint256 _initCapital + ) external view returns (uint256 _cTypeLoanAmount, uint256 _leveragedDebt) { + (_cTypeLoanAmount, _leveragedDebt) = _getLeveragedDebt(_cType, _initCapital, 0); } - /// @dev exact-in sell swap on ParaSwap - function sellOnParaSwap( - SellParams memory _sellParams, - uint256 _minDstAmount - ) external returns (uint256 _amountReceived) { - _amountReceived = _sellOnParaSwap( - _sellParams.offset, - _sellParams.swapCalldata, - IERC20Metadata(_sellParams.fromToken), - IERC20Metadata(_sellParams.toToken), - _sellParams.sellAmount, - _minDstAmount - ); + /// @dev get collateral loan and leveraged debt with percentage buffer + function getLeveragedDebt( + bytes32 _cType, + uint256 _initCapital, + uint256 _percentageBuffer + ) external view returns (uint256 _cTypeLoanAmount, uint256 _leveragedDebt) { + (_cTypeLoanAmount, _leveragedDebt) = _getLeveragedDebt(_cType, _initCapital, _percentageBuffer); } /// @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, bytes32 _cType ) external { + address _collateralJoin = collateralJoinFactory.collateralJoins(_cType); + + // transfer initial collateral deposit + ICollateralJoin(_collateralJoin).collateral().transferFrom(msg.sender, address(this), _initCollateral); + // deposit collateral, generate debt bytes memory _payload = abi.encodeWithSelector( exitActions.lockTokenCollateralAndGenerateDebtToAccount.selector, address(this), address(safeManager), - address(collateralJoinFactory.collateralJoins(_cType)), + _collateralJoin, coinJoin, _safeId, - _collateralLoan - PREMIUM, + _initCollateral + _collateralLoan, _sellParams.sellAmount ); @@ -124,7 +111,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte receiverAddress: address(this), asset: address(_sellParams.toToken), amount: _collateralLoan, - params: abi.encode(_minDstAmount, _sellParams, _payload), + params: abi.encode(_minDstAmount, _safeId, _collateralJoin, _sellParams, _payload), referralCode: uint16(block.number) }); } @@ -134,49 +121,78 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte address asset, uint256 amount, uint256 premium, - address initiator, + address, /* initiator */ bytes calldata params ) external override returns (bool) { - (uint256 _minDstAmount, SellParams memory _sellParams, bytes memory _payload) = - abi.decode(params, (uint256, SellParams, bytes)); + ( + uint256 _minDstAmount, + uint256 _safeId, + address _collateralJoin, + SellParams memory _sellParams, + bytes memory _payload + ) = abi.decode(params, (uint256, uint256, address, SellParams, bytes)); - emit log_named_uint('RETH BAL AQUIRE LOAN', IERC20Metadata(_sellParams.toToken).balanceOf(address(this))); + if (asset != _sellParams.toToken) revert WrongAsset(); + IERC20Metadata _toToken = IERC20Metadata(_sellParams.toToken); + IERC20Metadata _fromToken = IERC20Metadata(_sellParams.fromToken); - uint256 _beforebalance = IERC20Metadata(_sellParams.fromToken).balanceOf(address(this)); - uint256 _sellAmount = _sellParams.sellAmount; + { + uint256 _beforebalance = _fromToken.balanceOf(address(this)); + uint256 _sellAmount = _sellParams.sellAmount; - _executeFromProxy(_payload); + _executeFromProxy(_payload); - // todo add error msg - // if (_sellAmount != OD.balanceOf(address(this)) - _beforebalance) revert(); - - // swap debt to collateral - _sellOnParaSwap( - _sellParams.offset, - _sellParams.swapCalldata, - IERC20Metadata(_sellParams.fromToken), - IERC20Metadata(_sellParams.toToken), - _sellAmount, - _minDstAmount - ); - emit log_named_uint('RETH BAL POST SWAP', IERC20Metadata(_sellParams.toToken).balanceOf(address(this))); + if (_sellAmount != _fromToken.balanceOf(address(this)) - _beforebalance) revert IncorrectAmount(); + // swap debt to collateral + _sellOnParaSwap(_sellParams.offset, _sellParams.swapCalldata, _fromToken, _toToken, _sellAmount, _minDstAmount); + } uint256 _payBack = amount + premium; + uint256 _remainder = _toToken.balanceOf(address(this)) - _payBack; + if (_remainder > 0) { + bytes memory _returnPayload = abi.encodeWithSelector( + exitActions.lockTokenCollateral.selector, address(safeManager), _collateralJoin, _safeId, _remainder + ); + _executeFromProxy(_returnPayload); + } + IERC20Metadata(asset).approve(address(POOL), _payBack); return true; } + /// @dev get safetyRatio as fixed-point percent + function getSafetyRatio(bytes32 _cType) public view returns (uint256 _safetyCRatio) { + IOracleRelayer.OracleRelayerCollateralParams memory _cParams = oracleRelayer.cParams(_cType); + _safetyCRatio = _cParams.safetyCRatio / 1e25; + } + + /// @dev get accumulated rate and safety price for a cType from the SAFEEngine + function _getCData(bytes32 _cType) internal view returns (uint256 _accumulatedRate, uint256 _safetyPrice) { + ISAFEEngine.SAFEEngineCollateralData memory _safeEngCData = ISAFEEngine(safeManager.safeEngine()).cData(_cType); + _accumulatedRate = _safeEngCData.accumulatedRate; + _safetyPrice = _safeEngCData.safetyPrice; + } + + /// @dev calculate collateral loan amount and leveraged debt + function _getLeveragedDebt( + bytes32 _cType, + uint256 _initCapital, + uint256 _percentageBuffer + ) internal view returns (uint256 _cTypeLoanAmount, uint256 _leveragedDebt) { + (uint256 _accumulatedRate, uint256 _safetyPrice) = _getCData(_cType); + + uint256 _percent = getSafetyRatio(_cType) + _percentageBuffer; + uint256 _multiplier = 10_000 / (105 - (10_000 / (_percent))); + + _cTypeLoanAmount = (_initCapital * _multiplier / 100) - _initCapital; + _leveragedDebt = _initCapital.wmul(_safetyPrice).wdiv(_accumulatedRate) * _multiplier / 100; + } + /// @dev execute payload with delegate call via proxy for address(this) function _executeFromProxy(bytes memory _payload) internal { PS_ADAPTER_ODPROXY.execute(address(exitActions), _payload); } - /// @dev transfer asset to this contract to use in flashloan-swap - function _deposit(address _account, address _asset, uint256 _amount) internal { - IERC20Metadata(_asset).transferFrom(_account, address(this), _amount); - _deposits[_account][_asset] = _amount; - } - /// @dev takes ParaSwap transaction data and executes sell swap function _sellOnParaSwap( uint256 _offset, @@ -212,10 +228,32 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte } } uint256 _amountSold = _initBalFromToken - _fromToken.balanceOf(address(this)); - if (_sellAmount > _amountSold) revert OverSell(); + if (_sellAmount < _amountSold) revert OverSell(); _amountReceived = _toToken.balanceOf(address(this)) - _initBalToToken; - if (_amountReceived < _minDstAmount) revert UnderBuy(); + uint256 _amountAccepted = _minDstAmount - _minDstAmount.percentMul(MAX_SLIPPAGE_PERCENT); + if (_amountReceived < _amountAccepted) revert UnderBuy(); emit Swapped(address(_fromToken), address(_toToken), _amountSold, _amountReceived); } + + /// @notice overridden function from Modifiable to modify parameters + function _modifyParameters(bytes32 _param, bytes memory _data) internal override { + address _addr = _data.toAddress(); + + if (_param == 'augustus') { + augustus = IParaswapAugustus(_addr.assertNonNull()); + } else if (_param == 'safeManager') { + safeManager = IODSafeManager(_addr.assertNonNull()); + } else if (_param == 'oracleRelayer') { + oracleRelayer = IOracleRelayer(_addr.assertNonNull()); + } else if (_param == 'collateralJoinFactory') { + collateralJoinFactory = ICollateralJoinFactory(_addr.assertNonNull()); + } else if (_param == 'exitActions') { + exitActions = IExitActions(_addr.assertNonNull()); + } else if (_param == 'coinJoin') { + coinJoin = _addr.assertNonNull(); + } else { + revert UnrecognizedParam(); + } + } } diff --git a/src/leverage/interfaces/IExitActions.sol b/src/leverage/interfaces/IExitActions.sol new file mode 100644 index 0000000..cf25a6f --- /dev/null +++ b/src/leverage/interfaces/IExitActions.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.20; + +import {IBasicActions} from '@opendollar/interfaces/proxies/actions/IBasicActions.sol'; + +interface IExitActions is IBasicActions { + /// @dev exits `_coinsToExit` system coins `_contract` + function exitSystemCoinsToAccount(address _contract, address _coinJoin, uint256 _coinsToExit) external; + + /// @dev exits all system coins `_contract` + function exitAllSystemCoinsToAccount(address _contract, address _coinJoin) external; + + /// @dev generate debt to `_contract` + function generateDebtToAccount( + address _contract, + address _manager, + address _coinJoin, + uint256 _safeId, + uint256 _deltaWad + ) external; + + /// @dev generate debt to user proxy + function generateDebtToProxy(address _manager, address _coinJoin, uint256 _safeId, uint256 _deltaWad) external; + + /// @dev generate debt without exit + function generateInternalDebt(address _manager, uint256 _safeId, uint256 _deltaWad) external; + + /// @dev lock collateral and generate debt to `_contract` + function lockTokenCollateralAndGenerateDebtToAccount( + address _contract, + address _manager, + address _collateralJoin, + address _coinJoin, + uint256 _safe, + uint256 _collateralAmount, + uint256 _deltaWad + ) external; +} diff --git a/src/leverage/interfaces/IParaswapSellAdapter.sol b/src/leverage/interfaces/IParaswapSellAdapter.sol index 70aa514..b8f5e36 100644 --- a/src/leverage/interfaces/IParaswapSellAdapter.sol +++ b/src/leverage/interfaces/IParaswapSellAdapter.sol @@ -3,8 +3,30 @@ pragma solidity 0.8.20; import {IPoolAddressesProvider} from '@aave-core-v3/contracts/interfaces/IPoolAddressesProvider.sol'; import {IPool} from '@aave-core-v3/contracts/interfaces/IPool.sol'; +import {IModifiable} from '@opendollar/interfaces/utils/IModifiable.sol'; -interface IParaswapSellAdapter { +/** + * _augustusRegistry address of Paraswap AugustusRegistry + * _augustusSwapper address of Paraswap AugustusSwapper + * _poolProvider address of Aave PoolAddressProvider + * _vault721 address of OpenDollar Vault721 + * _oracleRelayer address of OpenDollar OracleRelayer + * _exitActions address of OpenDollar ExitActions + * _collateralJoinFactory address of OpenDollar CollateralJoinFactory + * _coinJoin address of OpenDollar CoinJoin + */ +struct InitSellAdapter { + address augustusRegistry; + address augustusSwapper; + address poolProvider; + address vault721; + address oracleRelayer; + address exitActions; + address collateralJoinFactory; + address coinJoin; +} + +interface IParaswapSellAdapter is IModifiable { /** * @dev emitted after a sell of an asset is made * @param _fromAsset address of the asset sold @@ -16,10 +38,12 @@ interface IParaswapSellAdapter { error InvalidAugustus(); error InsufficientBalance(); + error IncorrectAmount(); error OffsetOutOfRange(); error OverSell(); error UnderBuy(); error ZeroValue(); + error WrongAsset(); /** * @param _offset offset of fromAmount in Augustus calldata if it should be overwritten, otherwise 0 @@ -36,16 +60,6 @@ interface IParaswapSellAdapter { uint256 sellAmount; } - /** - * @param _sellParams IParaswapSellAdapter.SellParams - * @param _minDstAmount for sell/swap - * @return _amountReceived amount of asset bought - */ - function sellOnParaSwap( - SellParams memory _sellParams, - uint256 _minDstAmount - ) external returns (uint256 _amountReceived); - /** * @param _sellParams IParaswapSellAdapter.SellParams * @param _minDstAmount accepted for sell/swap @@ -54,6 +68,7 @@ interface IParaswapSellAdapter { */ function requestFlashloan( SellParams memory _sellParams, + uint256 _initCollateral, uint256 _collateralLoan, uint256 _minDstAmount, uint256 _safeId, @@ -61,15 +76,32 @@ interface IParaswapSellAdapter { ) external; /** - * @param _asset token - * @param _amount to deposit + * @param _cType collateral type of OpenDollar NFV/CDP */ - function deposit(address _asset, uint256 _amount) external; + function getCData(bytes32 _cType) external view returns (uint256 _accumulatedRate, uint256 _safetyPrice); /** - * @param _onBehalfOf account to receive balance - * @param _asset token - * @param _amount to deposit + * @param _cType collateral type of OpenDollar NFV/CDP + */ + function getSafetyRatio(bytes32 _cType) external returns (uint256 _safetyCRatio); + + /** + * @param _cType collateral type of OpenDollar NFV/CDP + * @param _initCapital initial collateral deposit + */ + function getLeveragedDebt( + bytes32 _cType, + uint256 _initCapital + ) external returns (uint256 _cTypeLoanAmount, uint256 _leveragedDebt); + + /** + * @param _cType collateral type of OpenDollar NFV/CDP + * @param _initCapital initial collateral deposit + * @param _percentageBuffer percentage above cType safetyRatio */ - function deposit(address _onBehalfOf, address _asset, uint256 _amount) external; + function getLeveragedDebt( + bytes32 _cType, + uint256 _initCapital, + uint256 _percentageBuffer + ) external returns (uint256 _cTypeLoanAmount, uint256 _leveragedDebt); } diff --git a/test/e2e/E2ELeverageCalculator.t.sol b/test/e2e/E2ELeverageCalculator.t.sol deleted file mode 100644 index 4f2591b..0000000 --- a/test/e2e/E2ELeverageCalculator.t.sol +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.20; - -import {MintableERC20} from '@opendollar/contracts/for-test/MintableERC20.sol'; -import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; -import {MintableERC20} from '@opendollar/contracts/for-test/MintableERC20.sol'; -import {TKN} from '@opendollar/test/e2e/Common.t.sol'; -import {CommonTest} from 'test/e2e/common/CommonTest.t.sol'; - -contract E2ELeverageCalculator is CommonTest { - address public constant LEVERAGE_HANDLER = address(0x1111); - address public leverageHandlerProxy; - - function setUp() public virtual override { - super.setUp(); - aliceProxy = _deployOrFind(alice); - _openSafe(aliceProxy, TKN); - - MintableERC20(token).mint(alice, DEPOSIT); - - vm.prank(alice); - IERC20(token).approve(aliceProxy, type(uint256).max); - - aliceNFV = vault721.getNfvState(vaults[aliceProxy]); - - MintableERC20(token).mint(address(this), DEPOSIT); - - leverageHandlerProxy = _deployOrFind(LEVERAGE_HANDLER); - - vm.prank(LEVERAGE_HANDLER); - IERC20(token).approve(leverageHandlerProxy, type(uint256).max); - - vm.prank(aliceProxy); - safeManager.allowSAFE(vaults[aliceProxy], leverageHandlerProxy, true); - } - - function testLockCollateral() public { - /// @notice collateral locked - _lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); - (uint256 _collateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - assertEq(_collateral, DEPOSIT); - } - - /// @notice exited coins transfered to proxy - function testLockAndGenerateDebtToProxy() public { - _lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); - _genDebtToProxy(vaults[aliceProxy], MINT, aliceProxy); - (, uint256 _debt) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - assertEq(_debt, MINT); - uint256 _internalDebt = leverageCalculator.getCoinBalance(aliceProxy); - assertEq(_internalDebt, 0); - } - - /// @notice internal coins are available to proxy - function testLockAndGenerateInternalDebt() public { - _lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); - _genInternalDebt(vaults[aliceProxy], MINT, aliceProxy); - (, uint256 _debt) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - assertEq(_debt, MINT); - uint256 _internalDebt = leverageCalculator.getCoinBalance(aliceProxy); - assertEq(_internalDebt, _debt); - } - - /// @notice 1/2 internal coins were exited to proxy - function testLockAndGenerateInternalDebtAndPartialExitToProxy() public { - _lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); - _genInternalDebt(vaults[aliceProxy], MINT, aliceProxy); - assertEq(systemCoin.balanceOf(aliceProxy), 0); - - _exitCoinToAccount(aliceProxy, aliceProxy, MINT / 2); - (, uint256 _debt) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - assertEq(_debt, MINT); - - uint256 _internalDebt = leverageCalculator.getCoinBalance(aliceProxy); - assertEq(_internalDebt, _debt / 2); - assertEq(systemCoin.balanceOf(aliceProxy), MINT / 2); - } - - /// @notice leverageAmount used to make swap and leverage safe - function testCalcSingleSwap() public { - _lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); - (uint256 _initCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - - uint256 _leverageAmount = leverageCalculator.calculateSingleLeverage(vaults[aliceProxy]); - _genDebtToAccount(LEVERAGE_HANDLER, vaults[aliceProxy], _leverageAmount, aliceProxy); - assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), _leverageAmount); - - vm.prank(LEVERAGE_HANDLER); - _swapIn(LEVERAGE_HANDLER, _leverageAmount); - _swapOut(LEVERAGE_HANDLER); - - assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), 0); - assertEq(IERC20(token).balanceOf(LEVERAGE_HANDLER), _leverageAmount); - - vm.prank(LEVERAGE_HANDLER); - _lockCollateral(TKN, vaults[aliceProxy], _leverageAmount, leverageHandlerProxy); - (uint256 _finalCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - - /// @dev this maths because collateral and debt tokens are equal: 1-to-1 - assertEq(_initCollateral + _leverageAmount, _finalCollateral); - } - - /// @notice (leverageAmount - initial debt) used to make swap and leverage safe - function testCalcSingleSwapWithPreviousDebtMinted() public { - _lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); - (uint256 _initCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - uint256 _leverageAmount = leverageCalculator.calculateSingleLeverage(vaults[aliceProxy]); - - _genDebtToAccount(address(0x9999), vaults[aliceProxy], MINT / 2, aliceProxy); - uint256 _newLeverageAmount = leverageCalculator.calculateSingleLeverage(vaults[aliceProxy]); - assertEq(_newLeverageAmount, _leverageAmount - MINT / 2); - - _genDebtToAccount(LEVERAGE_HANDLER, vaults[aliceProxy], _newLeverageAmount, aliceProxy); - assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), _newLeverageAmount); - - vm.prank(LEVERAGE_HANDLER); - _swapIn(LEVERAGE_HANDLER, _newLeverageAmount); - _swapOut(LEVERAGE_HANDLER); - - assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), 0); - assertEq(IERC20(token).balanceOf(LEVERAGE_HANDLER), _newLeverageAmount); - - vm.prank(LEVERAGE_HANDLER); - _lockCollateral(TKN, vaults[aliceProxy], _newLeverageAmount, leverageHandlerProxy); - (uint256 _finalCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); - - /// @dev this maths because collateral and debt tokens are equal: 1-to-1 - assertEq(_initCollateral + _newLeverageAmount, _finalCollateral); - } - - /// SIMULATION FUNCTIONS - mapping(address => uint256) public swapIn; - - /** - * @dev to simulate swaps in tests - */ - function _swapIn(address _sender, uint256 _amount) internal { - require(systemCoin.transfer(address(this), _amount), 'SwapInFail'); - swapIn[_sender] = _amount; - } - - function _swapOut(address _receiver) internal { - IERC20(token).transfer(_receiver, swapIn[_receiver]); - } -} diff --git a/test/e2e/E2ESwapExit.t.sol b/test/e2e/E2ESwapExit.t.sol index fd07037..59a0c2d 100644 --- a/test/e2e/E2ESwapExit.t.sol +++ b/test/e2e/E2ESwapExit.t.sol @@ -2,111 +2,246 @@ pragma solidity 0.8.20; import '@script/Registry.s.sol'; +import {Strings} from '@openzeppelin/utils/Strings.sol'; import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; +import {ISAFEEngine} from '@opendollar/interfaces/ISAFEEngine.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 {ParaswapSellAdapter, IParaswapSellAdapter, InitSellAdapter} from 'src/leverage/ParaswapSellAdapter.sol'; import {CommonTest} from 'test/e2e/common/CommonTest.t.sol'; -import {Math} from '@opendollar/libraries/Math.sol'; +import {Math, WAD} from '@opendollar/libraries/Math.sol'; contract E2ESwapExit is CommonTest { using Math for uint256; + using Strings 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; - address public SELL_ADAPTER; + uint256 public constant PERCENT = 135; + IVault721.NFVState public userNFV; address public userProxy; - address public sellAdapterProxy; IParaswapSellAdapter public sellAdapter; - IVault721.NFVState public userNFV; + address public sellAdapterAddr; + address public sellAdapterProxy; IDenominatedOracle public rethOracle; - uint256 rethUsdPrice; + uint256 public rethUsdPrice; + + IDenominatedOracle public wstethOracle; + uint256 public wstethUsdPrice; + + IDenominatedOracle public arbOracle; + uint256 public arbUsdPrice; function setUp() public virtual override { super.setUp(); rethOracle = IDenominatedOracle(MAINNET_DENOMINATED_RETH_USD_ORACLE); (rethUsdPrice,) = rethOracle.getResultWithValidity(); - setCTypePrice(RETH, rethUsdPrice); + _setCTypePrice(RETH, rethUsdPrice); + + wstethOracle = IDenominatedOracle(MAINNET_DENOMINATED_WSTETH_USD_ORACLE); + (wstethUsdPrice,) = wstethOracle.getResultWithValidity(); + _setCTypePrice(WSTETH, wstethUsdPrice); + + arbOracle = IDenominatedOracle(MAINNET_CHAINLINK_ARB_USD_RELAYER); + (arbUsdPrice,) = arbOracle.getResultWithValidity(); + _setCTypePrice(ARB, arbUsdPrice); userProxy = _deployOrFind(USER); label(USER, 'USER-WALLET'); label(userProxy, 'USER-PROXY'); - _openSafe(userProxy, RETH); - vm.prank(USER); - IERC20Metadata(RETH_ADDR).approve(userProxy, type(uint256).max); - - userNFV = vault721.getNfvState(vaults[userProxy]); - - sellAdapter = new ParaswapSellAdapter( - address(systemCoin), + InitSellAdapter memory _init = InitSellAdapter( AugustusRegistry.ARBITRUM, PARASWAP_AUGUSTUS_SWAPPER, AAVE_POOL_ADDRESS_PROVIDER, address(vault721), + address(oracleRelayer), address(exitActions), address(collateralJoinFactory), address(coinJoin) ); - SELL_ADAPTER = address(sellAdapter); + sellAdapter = new ParaswapSellAdapter(_init); + sellAdapterAddr = address(sellAdapter); - sellAdapterProxy = _deployOrFind(SELL_ADAPTER); - label(SELL_ADAPTER, 'SELL-ADAPTER-CONTRACT'); + sellAdapterProxy = _deployOrFind(sellAdapterAddr); + label(sellAdapterAddr, 'SELL-ADAPTER-CONTRACT'); label(sellAdapterProxy, 'SELL-ADAPTER-PROXY'); - vm.startPrank(SELL_ADAPTER); + vm.startPrank(sellAdapterAddr); + IERC20Metadata(ARB_ADDR).approve(sellAdapterProxy, type(uint256).max); IERC20Metadata(RETH_ADDR).approve(sellAdapterProxy, type(uint256).max); + IERC20Metadata(WSTETH_ADDR).approve(sellAdapterProxy, type(uint256).max); IERC20Metadata(OD_ADDR).approve(sellAdapterProxy, type(uint256).max); vm.stopPrank(); } + function _requestFlashLoanAndAssertValues(uint256 _initCapital, bytes32 _cType) internal { + assertEq(collateral[_cType].balanceOf(sellAdapterAddr), 0); + assertEq(systemCoin.balanceOf(sellAdapterAddr), 0); + + _requestFlashLoan(_initCapital, _cType); + + assertEq(collateral[_cType].balanceOf(sellAdapterAddr), 0); + assertEq(systemCoin.balanceOf(sellAdapterAddr), 0); + } + /** - * 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 + * @dev maxLev = 1/(1-LTV) + * example: + * LTV = 66.7% = 0.667 + * maxLev = 1/(1 - 0.667) = 3 */ - function testRequestFlashloan() public { - assertEq(readCTypePrice(RETH), rethUsdPrice); - uint256 _collateralLoan = 1 ether; - uint256 _sellAmount = (_collateralLoan.wmul(rethUsdPrice) - PREMIUM) * 2 / 3; - emit log_named_uint('DEBT SELL AMOUNT', _sellAmount); + function _requestFlashLoan(uint256 _initCapital, bytes32 _cType) internal { + _setupUserSafe(_cType); + address _cTypeAddr = address(collateral[_cType]); + deal(_cTypeAddr, USER, _initCapital); - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = - _getFullUserInputWithAmount(OD_ADDR, RETH_ADDR, _sellAmount); + (uint256 _loanAmount, uint256 _leveragedDebt) = sellAdapter.getLeveragedDebt(_cType, _initCapital); + + /// @notice ParaSwap SDK call: get dstAmount from route + uint256 _swapResult = _getDstAmountUserInput(OD_ADDR, _cTypeAddr, _leveragedDebt); + + uint256 _accumulator; + while ((_loanAmount + PREMIUM) > _swapResult) { + _accumulator += 3; + (_loanAmount, _leveragedDebt) = sellAdapter.getLeveragedDebt(_cType, _initCapital, _accumulator); + + /// @notice ParaSwap SDK call: get dstAmount from route + _swapResult = _getDstAmountUserInput(OD_ADDR, _cTypeAddr, _leveragedDebt); + } + emit log_named_uint('LOOP ROUNDS', _accumulator / 3); - // todo: lock initial capital in safe - uint256 _initCapital = _collateralLoan / 2; - deal(RETH_ADDR, USER, _initCapital); + /// @notice ParaSwap SDK call: get transaction + (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = + _getFullUserInputWithAmount(OD_ADDR, _cTypeAddr, _leveragedDebt); + /// @notice USER approves sellAdapterProxy as handler of their safe vm.prank(userProxy); safeManager.allowSAFE(vaults[userProxy], sellAdapterProxy, true); + /// @notice USER deposits initialCollateral in sellAdapterProxy and executes flashloan leverage vm.startPrank(USER); - IERC20Metadata(RETH_ADDR).approve(SELL_ADAPTER, _initCapital); - sellAdapter.deposit(RETH_ADDR, _initCapital); + IERC20Metadata(_cTypeAddr).approve(sellAdapterAddr, _initCapital); + sellAdapter.requestFlashloan(_sellParams, _initCapital, _loanAmount, _dstAmount, vaults[userProxy], _cType); + vm.stopPrank(); - // assertEq(IERC20Metadata(RETH_ADDR).balanceOf(SELL_ADAPTER), _initCapital); - // assertEq(IERC20Metadata(OD_ADDR).balanceOf(SELL_ADAPTER), 0); + /// @notice log success values + _logFinalValues(_initCapital, _cType); + } - sellAdapter.requestFlashloan(_sellParams, _collateralLoan, _dstAmount, vaults[userProxy], RETH); - // assertEq(IERC20Metadata(RETH_ADDR).balanceOf(SELL_ADAPTER), 0); + function _setupUserSafe(bytes32 _cType) internal { + _openSafe(userProxy, _cType); + vm.prank(USER); + collateral[_cType].approve(userProxy, type(uint256).max); + userNFV = vault721.getNfvState(vaults[userProxy]); + } - vm.stopPrank(); + function _logFinalValues(uint256 _deposit, bytes32 _cType) internal { + (uint256 _c, uint256 _d) = _getSAFE(_cType, userNFV.safeHandler); + emit log_named_bytes32('COLLATERAL TYPE', _cType); + if (_cType == RETH) { + emit log_named_string('RETH ORACLE PRICE', _floatingPointWad(_readCTypePrice(RETH))); + } else if (_cType == WSTETH) { + emit log_named_string('WSTETH ORACLE PRICE', _floatingPointWad(_readCTypePrice(WSTETH))); + } else if (_cType == ARB) { + emit log_named_string('ARB ORACLE PRICE', _floatingPointWad(_readCTypePrice(ARB))); + } + emit log_named_string('SAFETYRATION PERCENT', string.concat(sellAdapter.getSafetyRatio(_cType).toString(), '%')); + emit log_named_string('--------------------', ''); + emit log_named_string('ORIGINAL DEBT', '0'); + emit log_named_string('FINAL DEBT', _floatingPointWad(_d)); + emit log_named_string('--------------------', ''); + emit log_named_string('ORIGINAL COLLATERAL', _floatingPointWad(_deposit)); + emit log_named_string('FINAL COLLATERAL', _floatingPointWad(_c)); + emit log_named_string('REMAINING COLLATERAL', _floatingPointWad(collateral[_cType].balanceOf(sellAdapterAddr))); + emit log_named_string('--------------------', ''); + uint256 divRes = _c * 100 / _deposit; + emit log_named_string('MULTIPLIER RATE', _floatingPointWad((divRes) * 1e16, 1e16)); + emit log_named_string('LEVERAGE PERCENTAGE', string.concat((divRes - 100).toString(), '%')); + } +} + +contract E2ESwapExitARB is E2ESwapExit { + function testRequestFlashloan0() public { + _requestFlashLoanAndAssertValues(0.00001 ether, ARB); + } + + function testRequestFlashloan1() public { + _requestFlashLoanAndAssertValues(0.1 ether, ARB); + } + + function testRequestFlashloan2() public { + _requestFlashLoanAndAssertValues(2 ether, ARB); + } + + function testRequestFlashloan3() public { + _requestFlashLoanAndAssertValues(4 ether, ARB); + } + + function testRequestFlashloan4() public { + _requestFlashLoanAndAssertValues(8 ether, ARB); + } + + function testRequestFlashloan5() public { + _requestFlashLoanAndAssertValues(16 ether, ARB); + } +} + +contract E2ESwapExitRETH is E2ESwapExit { + function testRequestFlashloan0() public { + _requestFlashLoanAndAssertValues(0.00001 ether, RETH); + } + + function testRequestFlashloan1() public { + _requestFlashLoanAndAssertValues(0.1 ether, RETH); + } + + function testRequestFlashloan2() public { + _requestFlashLoanAndAssertValues(2 ether, RETH); + } + + function testRequestFlashloan3() public { + _requestFlashLoanAndAssertValues(4 ether, RETH); + } + + function testRequestFlashloan4() public { + _requestFlashLoanAndAssertValues(8 ether, RETH); + } + + function testRequestFlashloan5() public { + _requestFlashLoanAndAssertValues(16 ether, RETH); + } +} + +contract E2ESwapExitWSTETH is E2ESwapExit { + function testRequestFlashloan0() public { + _requestFlashLoanAndAssertValues(0.00001 ether, WSTETH); + } + + function testRequestFlashloan1() public { + _requestFlashLoanAndAssertValues(0.1 ether, WSTETH); + } + + function testRequestFlashloan2() public { + _requestFlashLoanAndAssertValues(2 ether, WSTETH); + } + + function testRequestFlashloan3() public { + _requestFlashLoanAndAssertValues(4 ether, WSTETH); } - function setCTypePrice(bytes32 _cType, uint256 _price) public { - DelayedOracleForTest(address(delayedOracle[_cType])).setPriceAndValidity(_price, true); - oracleRelayer.updateCollateralPrice(_cType); + function testRequestFlashloan4() public { + _requestFlashLoanAndAssertValues(8 ether, WSTETH); } - function readCTypePrice(bytes32 _cType) public returns (uint256 _price) { - _price = delayedOracle[_cType].read(); + function testRequestFlashloan5() public { + _requestFlashLoanAndAssertValues(16 ether, WSTETH); } } diff --git a/test/e2e/E2ESwapSell.t.sol b/test/e2e/E2ESwapSell.t.sol deleted file mode 100644 index 35cb379..0000000 --- a/test/e2e/E2ESwapSell.t.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.20; - -import '@script/Registry.s.sol'; -import {AugustusRegistry} from '@aave-debt-swap/dependencies/paraswap/AugustusRegistry.sol'; -import {ParaswapSellAdapter, IParaswapSellAdapter} from 'src/leverage/ParaswapSellAdapter.sol'; -import {BaseTest} from 'test/e2e/common/BaseTest.t.sol'; -import {BytesLib} from 'src/library/BytesLib.sol'; - -contract E2ESwapSell is BaseTest { - IParaswapSellAdapter public sellAdapter; - - function setUp() public virtual { - vm.createSelectFork(vm.rpcUrl('mainnet')); - sellAdapter = new ParaswapSellAdapter( - address(0xaaa), - AugustusRegistry.ARBITRUM, - PARASWAP_AUGUSTUS_SWAPPER, - AAVE_POOL_ADDRESS_PROVIDER, - address(VAULT721), - address(0x123), - address(0x456), - address(0x789) - ); - } - - function testSwapRethToWeth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(RETH_ADDR, WETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), RETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapWethToReth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(WETH_ADDR, RETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), WETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapWstethToWeth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(WSTETH_ADDR, WETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), WSTETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapWethToWsteth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(WETH_ADDR, WSTETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), WETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapWstethToReth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(WSTETH_ADDR, RETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), WSTETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapRethToWsteth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(RETH_ADDR, WSTETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), RETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapRethToOpenDollar() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(RETH_ADDR, OD_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), RETH_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } - - function testSwapOpenDollarToReth() public { - (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInput(OD_ADDR, RETH_ADDR); - - vm.startPrank(USER); - _supplyAndDeposit(address(sellAdapter), OD_ADDR, SELL_AMOUNT); - sellAdapter.sellOnParaSwap(_sellParams, _dstAmount); - vm.stopPrank(); - } -} diff --git a/test/e2e/common/BaseTest.t.sol b/test/e2e/common/BaseTest.t.sol index 0b3d946..6967f84 100644 --- a/test/e2e/common/BaseTest.t.sol +++ b/test/e2e/common/BaseTest.t.sol @@ -10,7 +10,15 @@ contract BaseTest is Test { function _supplyAndDeposit(address _adapter, address _asset, uint256 _amount) internal { deal(_asset, msg.sender, _amount); IERC20Metadata(_asset).approve(address(_adapter), _amount); - IParaswapSellAdapter(_adapter).deposit(_asset, _amount); + IERC20Metadata(_asset).transferFrom(msg.sender, address(this), _amount); + } + + function _getDstAmountUserInput( + address _fromToken, + address _toToken, + uint256 _amount + ) internal returns (uint256 _dstAmount) { + _dstAmount = uint256(bytes32(_getDstAmount(_fromToken, 18, _toToken, 18, _amount, USER))); } function _getSingleUserInput( @@ -18,7 +26,7 @@ contract BaseTest is Test { address _toToken ) internal returns (IParaswapSellAdapter.SellParams memory _sellParams) { deal(_fromToken, USER, SELL_AMOUNT); - bytes memory _result = _getSwapRoute(_fromToken, 18, _toToken, 18, SELL_AMOUNT, USER); + bytes memory _result = _getSwapTransaction(_fromToken, 18, _toToken, 18, SELL_AMOUNT, USER); _sellParams = IParaswapSellAdapter.SellParams(0, _result, _fromToken, _toToken, SELL_AMOUNT); } @@ -36,7 +44,7 @@ contract BaseTest is Test { ) internal returns (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) { deal(_fromToken, USER, _amount); _dstAmount = uint256(bytes32(_getDstAmount(_fromToken, 18, _toToken, 18, _amount, USER))); - bytes memory _result = _getSwapRoute(_fromToken, 18, _toToken, 18, _amount, USER); + bytes memory _result = _getSwapTransaction(_fromToken, 18, _toToken, 18, _amount, USER); _sellParams = IParaswapSellAdapter.SellParams(0, _result, _fromToken, _toToken, _amount); } @@ -61,7 +69,7 @@ contract BaseTest is Test { _result = vm.ffi(inputs); } - function _getSwapRoute( + function _getSwapTransaction( address _fromToken, uint256 _fromDecimals, address _toToken, @@ -71,7 +79,7 @@ contract BaseTest is Test { ) internal returns (bytes memory _result) { string[] memory inputs = new string[](8); inputs[0] = 'node'; - inputs[1] = './script/getSwapRoute.js'; + inputs[1] = './script/getSwapTransaction.js'; inputs[2] = vm.toString(_fromToken); inputs[3] = vm.toString(_fromDecimals); inputs[4] = vm.toString(_toToken); @@ -82,11 +90,3 @@ contract BaseTest is Test { _result = vm.ffi(inputs); } } - -// function _borrow(address _adapter, address _asset, uint256 _amount) internal { -// _adapter.borrow(_asset, _amount, 2, 0, USER); -// } - -// function _withdraw(address _adapter, address _asset, uint256 _amount) internal { -// _adapter.withdraw(_asset, _amount, USER); -// } diff --git a/test/e2e/common/CommonTest.t.sol b/test/e2e/common/CommonTest.t.sol index 9bb3a2b..dfb46a9 100644 --- a/test/e2e/common/CommonTest.t.sol +++ b/test/e2e/common/CommonTest.t.sol @@ -1,17 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; -import {Common, TKN} from '@opendollar/test/e2e/Common.t.sol'; +import {Strings} from '@openzeppelin/utils/Strings.sol'; 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 {Common, TKN} from '@opendollar/test/e2e/Common.t.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'; contract CommonTest is Common, BaseTest { using Math for uint256; + using Strings for uint256; uint256 public constant DEPOSIT = 10_000 ether; uint256 public constant MINT = DEPOSIT * 2 / 3; @@ -25,17 +27,23 @@ contract CommonTest is Common, BaseTest { IVault721.NFVState public aliceNFV; ExitActions public exitActions; - LeverageCalculator public leverageCalculator; - - mapping(address proxy => uint256 safeId) public vaults; function setUp() public virtual override { + _isCastTokens = true; super.setUp(); exitActions = new ExitActions(); - leverageCalculator = new LeverageCalculator(address(vault721)); 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 view returns (uint256 _price) { + _price = delayedOracle[_cType].read(); + } + function _deployOrFind(address _owner) internal returns (address _proxy) { _proxy = vault721.getProxy(_owner); if (_proxy == address(0)) { @@ -168,4 +176,23 @@ contract CommonTest is Common, BaseTest { _collateral = _safeData.lockedCollateral; _debt = _safeData.generatedDebt; } + + function _floatingPointWad(uint256 _num) internal pure returns (string memory _formatNum) { + uint256 _left = _num / 1e18; + uint256 _expLeft = _left * 1e18; + uint256 _right = _num - _expLeft; + + return string.concat(_left.toString(), '.', _right.toString()); + } + + function _floatingPointWad(uint256 _num, uint256 _decimalDivider) internal pure returns (string memory _formatNum) { + uint256 _left = _num / 1e18; + uint256 _expLeft = _left * 1e18; + uint256 _expRight = _num - _expLeft; + uint256 _right; + if (_decimalDivider > 0 && _decimalDivider < 1e18 + 1) _right = _expRight / _decimalDivider; + else _right = _expRight; + + return string.concat(_left.toString(), '.', _right.toString()); + } } diff --git a/yarn.lock b/yarn.lock index ae7e46c..72e9b05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -414,10 +414,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@opendollar/contracts@0.0.0-984c17c2": - version "0.0.0-984c17c2" - resolved "https://registry.yarnpkg.com/@opendollar/contracts/-/contracts-0.0.0-984c17c2.tgz#52a03256654cc7fe69cff430d60bed07d9a8648a" - integrity sha512-4AV3GKyi+deaSq1jsSUSP22/hkywYhF0EOE2v7cq7MnK3fuSKOSChKmLO0Hx/1PrRxhoE3/eIxqQuaPEqhpt0A== +"@opendollar/contracts@0.0.0-00b33fa2": + version "0.0.0-00b33fa2" + resolved "https://registry.yarnpkg.com/@opendollar/contracts/-/contracts-0.0.0-00b33fa2.tgz#bf54539bf9ea49032bb23f8b008fee132f08aa4c" + integrity sha512-u5T+UTn2s+93zb14TvKI+2mXpMv0JZAr5wfuL433aj0i4yPY45uYcGyOMYhQi0bAPEmCJ42STS/ualfC1RdnwA== dependencies: "@defi-wonderland/solidity-utils" "0.0.0-4298c6c6" "@openzeppelin/contracts" "4.9.6"