diff --git a/package.json b/package.json index 1ee8e91..1b76053 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-d805d33e", + "@opendollar/contracts": "0.0.0-984c17c2", "@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/Registry.s.sol b/script/Registry.s.sol index 664f0e1..d1fde6e 100644 --- a/script/Registry.s.sol +++ b/script/Registry.s.sol @@ -37,3 +37,9 @@ bytes32 constant WSTETH_HASH = bytes32(keccak256('WSTETH')); bytes32 constant AAVE_RETH_HASH = bytes32(keccak256('AAVE_RETH')); bytes32 constant AAVE_WETH_HASH = bytes32(keccak256('AAVE_WETH')); bytes32 constant AAVE_WSTETH_HASH = bytes32(keccak256('AAVE_WSTETH')); + +// Oracle Relayers +address constant MAINNET_CHAINLINK_ETH_USD_RELAYER = 0x3e6C1621f674da311E57646007fBfAd857084383; +address constant MAINNET_DENOMINATED_WSTETH_USD_ORACLE = 0xD0cf1FfFF3FB90c87210D76DFBc3AcfFd02D6B12; +address constant MAINNET_DENOMINATED_RETH_USD_ORACLE = 0x2b6b76D9854E9A7189c2F1b496c10043b373e453; +address constant MAINNET_CHAINLINK_ARB_USD_RELAYER = 0x2635f731BB6981E72F92A781578952450759F762; diff --git a/src/leverage/ParaswapSellAdapter.sol b/src/leverage/ParaswapSellAdapter.sol index 52193c5..107641d 100644 --- a/src/leverage/ParaswapSellAdapter.sol +++ b/src/leverage/ParaswapSellAdapter.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.20; import 'forge-std/Test.sol'; -import {IERC20} from '@openzeppelin/token/ERC20/IERC20.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'; @@ -27,9 +26,11 @@ import {ExitActions} from 'src/leverage/ExitActions.sol'; 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; IParaSwapAugustusRegistry public immutable AUGUSTUS_REGISTRY; ODProxy public immutable PS_ADAPTER_ODPROXY; + IERC20Metadata public immutable OPEN_DOLLAR; IParaswapAugustus public augustus; @@ -51,6 +52,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte * @param _coinJoin address of OpenDollar CoinJoin */ constructor( + address _systemCoin, address _augustusRegistry, address _augustusSwapper, address _poolProvider, @@ -59,6 +61,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte address _collateralJoinFactory, address _coinJoin ) FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_poolProvider)) { + OPEN_DOLLAR = IERC20Metadata(_systemCoin); AUGUSTUS_REGISTRY = IParaSwapAugustusRegistry(_augustusRegistry); augustus = IParaswapAugustus(_augustusSwapper); IVault721 _v721 = IVault721(_vault721); @@ -97,13 +100,11 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte /// @dev approve address(this) as safeHandler and request to borrow asset on Aave function requestFlashloan( SellParams memory _sellParams, + uint256 _collateralLoan, uint256 _minDstAmount, uint256 _safeId, bytes32 _cType ) external { - // how much - uint256 _collateralAmount = 1 ether; - // deposit collateral, generate debt bytes memory _payload = abi.encodeWithSelector( exitActions.lockTokenCollateralAndGenerateDebtToAccount.selector, @@ -112,7 +113,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte address(collateralJoinFactory.collateralJoins(_cType)), coinJoin, _safeId, - _collateralAmount, + _collateralLoan - PREMIUM, _sellParams.sellAmount ); @@ -120,7 +121,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte POOL.flashLoanSimple({ receiverAddress: address(this), asset: address(_sellParams.toToken), - amount: _collateralAmount, + amount: _collateralLoan, params: abi.encode(_minDstAmount, _sellParams, _payload), referralCode: uint16(block.number) }); @@ -137,21 +138,15 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte (uint256 _minDstAmount, SellParams memory _sellParams, bytes memory _payload) = abi.decode(params, (uint256, SellParams, bytes)); - uint256 _beforebalance = IERC20(_sellParams.fromToken).balanceOf(address(this)); - uint256 _sellAmount = _sellParams.sellAmount; + emit log_named_uint('RETH BAL AQUIRE LOAN', IERC20Metadata(_sellParams.toToken).balanceOf(address(this))); - emit log_named_uint('RETH BALANCE W LOAN', IERC20(_sellParams.toToken).balanceOf(address(this))); + uint256 _beforebalance = IERC20Metadata(_sellParams.fromToken).balanceOf(address(this)); + uint256 _sellAmount = _sellParams.sellAmount; - // generate debt _executeFromProxy(_payload); // todo add error msg // if (_sellAmount != OD.balanceOf(address(this)) - _beforebalance) revert(); - emit log_named_uint('OPEN DOLLAR BALANCE', IERC20(_sellParams.fromToken).balanceOf(address(this))); - - emit log_named_uint('RETH BAL POST-DEPOS', IERC20(_sellParams.toToken).balanceOf(address(this))); - - // ISAFEEngine(safeManager.safeEngine()).safes(); // swap debt to collateral _sellOnParaSwap( @@ -162,16 +157,15 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte _sellAmount, _minDstAmount ); + emit log_named_uint('RETH BAL POST SWAP', IERC20Metadata(_sellParams.toToken).balanceOf(address(this))); uint256 _payBack = amount + premium; - IERC20(asset).approve(address(POOL), _payBack); + IERC20Metadata(asset).approve(address(POOL), _payBack); return true; } function _executeFromProxy(bytes memory _payload) internal { - emit log_named_address('MSG SENDER', msg.sender); - // lock collateral on behalf of user and generate debt to address(this) PS_ADAPTER_ODPROXY.execute(address(exitActions), _payload); } @@ -194,7 +188,7 @@ contract ParaswapSellAdapter is FlashLoanSimpleReceiverBase, IParaswapSellAdapte if (_minDstAmount == 0) revert ZeroValue(); uint256 _initBalFromToken = _fromToken.balanceOf(address(this)); - // if (_initBalFromToken < _sellAmount) revert InsufficientBalance(); + if (_initBalFromToken < _sellAmount) revert InsufficientBalance(); uint256 _initBalToToken = _toToken.balanceOf(address(this)); address _tokenTransferProxy = augustus.getTokenTransferProxy(); diff --git a/src/leverage/interfaces/IParaswapSellAdapter.sol b/src/leverage/interfaces/IParaswapSellAdapter.sol index fc9333d..70aa514 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 _collateralLoan, uint256 _minDstAmount, uint256 _safeId, bytes32 _cType diff --git a/test/e2e/E2ESwapExit.t.sol b/test/e2e/E2ESwapExit.t.sol index ba9caea..fd07037 100644 --- a/test/e2e/E2ESwapExit.t.sol +++ b/test/e2e/E2ESwapExit.t.sol @@ -2,14 +2,18 @@ pragma solidity 0.8.20; import '@script/Registry.s.sol'; -import {IERC20} from '@openzeppelin/token/ERC20/IERC20.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'; +import {Math} from '@opendollar/libraries/Math.sol'; contract E2ESwapExit is CommonTest { + using Math for uint256; + uint256 public constant PREMIUM = 500_000_000_000; uint256 public constant INTEREST_RATE_MODE = 0; uint16 public constant REF_CODE = 0; @@ -21,17 +25,27 @@ contract E2ESwapExit is CommonTest { IParaswapSellAdapter public sellAdapter; IVault721.NFVState public userNFV; + IDenominatedOracle public rethOracle; + uint256 rethUsdPrice; + function setUp() public virtual override { super.setUp(); + rethOracle = IDenominatedOracle(MAINNET_DENOMINATED_RETH_USD_ORACLE); + (rethUsdPrice,) = rethOracle.getResultWithValidity(); + setCTypePrice(RETH, rethUsdPrice); + userProxy = _deployOrFind(USER); + label(USER, 'USER-WALLET'); + label(userProxy, 'USER-PROXY'); _openSafe(userProxy, RETH); vm.prank(USER); - IERC20(RETH_ADDR).approve(userProxy, type(uint256).max); + IERC20Metadata(RETH_ADDR).approve(userProxy, type(uint256).max); userNFV = vault721.getNfvState(vaults[userProxy]); sellAdapter = new ParaswapSellAdapter( + address(systemCoin), AugustusRegistry.ARBITRUM, PARASWAP_AUGUSTUS_SWAPPER, AAVE_POOL_ADDRESS_PROVIDER, @@ -44,55 +58,55 @@ contract E2ESwapExit is CommonTest { SELL_ADAPTER = address(sellAdapter); sellAdapterProxy = _deployOrFind(SELL_ADAPTER); + label(SELL_ADAPTER, 'SELL-ADAPTER-CONTRACT'); + label(sellAdapterProxy, 'SELL-ADAPTER-PROXY'); - vm.prank(SELL_ADAPTER); - IERC20(RETH_ADDR).approve(sellAdapterProxy, type(uint256).max); + vm.startPrank(SELL_ADAPTER); + IERC20Metadata(RETH_ADDR).approve(sellAdapterProxy, type(uint256).max); + IERC20Metadata(OD_ADDR).approve(sellAdapterProxy, type(uint256).max); + 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 { - uint256 _sellAmount = 1400 ether; + assertEq(readCTypePrice(RETH), rethUsdPrice); + uint256 _collateralLoan = 1 ether; + uint256 _sellAmount = (_collateralLoan.wmul(rethUsdPrice) - PREMIUM) * 2 / 3; + emit log_named_uint('DEBT SELL AMOUNT', _sellAmount); - // from OD to RETH (uint256 _dstAmount, IParaswapSellAdapter.SellParams memory _sellParams) = _getFullUserInputWithAmount(OD_ADDR, RETH_ADDR, _sellAmount); - deal(RETH_ADDR, USER, PREMIUM); + // 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); - IERC20(RETH_ADDR).approve(SELL_ADAPTER, PREMIUM); - sellAdapter.deposit(RETH_ADDR, PREMIUM); + IERC20Metadata(RETH_ADDR).approve(SELL_ADAPTER, _initCapital); + sellAdapter.deposit(RETH_ADDR, _initCapital); - assertEq(IERC20(RETH_ADDR).balanceOf(SELL_ADAPTER), PREMIUM); - assertEq(IERC20(OD_ADDR).balanceOf(SELL_ADAPTER), 0); + // assertEq(IERC20Metadata(RETH_ADDR).balanceOf(SELL_ADAPTER), _initCapital); + // assertEq(IERC20Metadata(OD_ADDR).balanceOf(SELL_ADAPTER), 0); - sellAdapter.requestFlashloan(_sellParams, _dstAmount, vaults[userProxy], RETH); - // assertEq(IERC20(RETH_ADDR).balanceOf(SELL_ADAPTER), 0); + sellAdapter.requestFlashloan(_sellParams, _collateralLoan, _dstAmount, vaults[userProxy], RETH); + // assertEq(IERC20Metadata(RETH_ADDR).balanceOf(SELL_ADAPTER), 0); vm.stopPrank(); } - /** - * @dev lock collateral in USER safe from sellAdapterProxy - * and mint debt from USER safe to sellAdapterProxy - */ - // function testLockCollateralFromHandler() public { - // deal(RETH_ADDR, SELL_ADAPTER, SELL_AMOUNT); - - // (uint256 _initCollateral,) = _getSAFE(RETH, userNFV.safeHandler); - // assertEq(_initCollateral, 0); - - // vm.prank(SELL_ADAPTER); - // _lockCollateral(RETH, vaults[userProxy], SELL_AMOUNT, sellAdapterProxy); - - // (uint256 _newCollateral,) = _getSAFE(RETH, userNFV.safeHandler); - // assertEq(_newCollateral, SELL_AMOUNT); + function setCTypePrice(bytes32 _cType, uint256 _price) public { + DelayedOracleForTest(address(delayedOracle[_cType])).setPriceAndValidity(_price, true); + oracleRelayer.updateCollateralPrice(_cType); + } - // assertEq(systemCoin.balanceOf(SELL_ADAPTER), 0); - // vm.prank(SELL_ADAPTER); - // _genDebtToAccount(SELL_ADAPTER, vaults[userProxy], SELL_AMOUNT * 2 / 3, sellAdapterProxy); - // assertEq(systemCoin.balanceOf(SELL_ADAPTER), SELL_AMOUNT * 2 / 3); - // } + function readCTypePrice(bytes32 _cType) public returns (uint256 _price) { + _price = delayedOracle[_cType].read(); + } } diff --git a/test/e2e/E2ESwapSell.t.sol b/test/e2e/E2ESwapSell.t.sol index 770f515..35cb379 100644 --- a/test/e2e/E2ESwapSell.t.sol +++ b/test/e2e/E2ESwapSell.t.sol @@ -13,6 +13,7 @@ contract E2ESwapSell is BaseTest { function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('mainnet')); sellAdapter = new ParaswapSellAdapter( + address(0xaaa), AugustusRegistry.ARBITRUM, PARASWAP_AUGUSTUS_SWAPPER, AAVE_POOL_ADDRESS_PROVIDER, diff --git a/yarn.lock b/yarn.lock index aae7db2..ae7e46c 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-d805d33e": - version "0.0.0-d805d33e" - resolved "https://registry.yarnpkg.com/@opendollar/contracts/-/contracts-0.0.0-d805d33e.tgz#ddab4b29d86478024a10a1e143419c96eba5c7e3" - integrity sha512-j/2DK1iziS4/ngHH4KyskBszHjXMp5fYhxNEyirxZYVSpJT4EMew6iaARYae5FCuLXgui7xxnvGp7lMtzgpt+g== +"@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== dependencies: "@defi-wonderland/solidity-utils" "0.0.0-4298c6c6" "@openzeppelin/contracts" "4.9.6"