From c104e731e076793929b346e6897e8e032fda8b73 Mon Sep 17 00:00:00 2001 From: Hunter King Date: Tue, 2 Jul 2024 21:08:11 -0600 Subject: [PATCH 1/7] add liq test --- test/e2e/E2ELiquidation.t.sol | 199 ++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 test/e2e/E2ELiquidation.t.sol diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol new file mode 100644 index 00000000..53c52c52 --- /dev/null +++ b/test/e2e/E2ELiquidation.t.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.20; + +import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; +import {ODProxy} from '@contracts/proxies/ODProxy.sol'; +import {IVault721} from '@interfaces/proxies/IVault721.sol'; +import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol'; +import {IOracleRelayer} from '@interfaces/IOracleRelayer.sol'; +import {Math, RAY} from '@libraries/Math.sol'; +import {DelayedOracleForTest} from '@test/mocks/DelayedOracleForTest.sol'; +import {Common, COLLAT, DEBT, TKN} from '@test/e2e/Common.t.sol'; + +uint256 constant MINUS_0_5_PERCENT_PER_HOUR = 999_998_607_628_240_588_157_433_861; +uint256 constant DEPOSIT = 135 ether + 1; // 136% collateralized +uint256 constant MINT = 100 ether; +uint256 constant DEFAULT_DEVALUATION = 0.2 ether; +uint256 constant USER_AMOUNT = 1000 ether; + +contract E2ELiquidation is Common { + using Math for uint256; + + uint256 public liquidationCRatio; // RAY + uint256 public safetyCRatio; // RAY + uint256 public accumulatedRate; // RAY + uint256 public liquidationPrice; // RAY + + ISAFEEngine.SAFEEngineCollateralData public cTypeData; + IOracleRelayer.OracleRelayerCollateralParams public oracleParams; + + IVault721.NFVState public aliceNFV; + IVault721.NFVState public bobNFV; + + address public aliceProxy; + address public bobProxy; + uint256 public initialSystemCoinSupply; + + mapping(address proxy => uint256 safeId) public vaults; + + function setUp() public virtual override { + super.setUp(); + _refreshCData(TKN); + + aliceProxy = _userVaultSetup(TKN, alice, USER_AMOUNT, 'AliceProxy'); + aliceNFV = vault721.getNfvState(vaults[aliceProxy]); + _depositCollateralAndGenDebt(TKN, vaults[aliceProxy], DEPOSIT, MINT, aliceProxy); + + bobProxy = _userVaultSetup(TKN, bob, USER_AMOUNT, 'BobProxy'); + bobNFV = vault721.getNfvState(vaults[bobProxy]); + _depositCollateralAndGenDebt(TKN, vaults[bobProxy], DEPOSIT * 3, MINT * 3, bobProxy); + + initialSystemCoinSupply = systemCoin.totalSupply(); + + _refreshCData(TKN); + + _collateralDevaluation(TKN, DEFAULT_DEVALUATION); + auctionId = liquidationEngine.liquidateSAFE(TKN, aliceNFV.safeHandler); + + vm.prank(bob); + systemCoin.approve(bobProxy, USER_AMOUNT); + } + + function testBuyCollateral1() public { + // CAH holds all 185 ether of collateral after liquidation and before auction + _logWadCollateralAuctionHouseTokenCollateral(TKN); + assertEq(safeEngine.tokenCollateral(TKN, address(collateralAuctionHouse[TKN])), DEPOSIT); + + // alice has no collateral after liquidation + assertEq(safeEngine.tokenCollateral(TKN, aliceNFV.safeHandler), 0); + + // bob's non-deposited collateral balance before collateral auction + uint256 _externalCollateralBalanceBob = collateral[TKN].balanceOf(bob); + + // alice + bob systemCoin supply + assertEq(initialSystemCoinSupply, systemCoin.totalSupply()); + + // bob to buy alice's liquidated collateral + _buyCollateral(TKN, auctionId, 0, MINT, bobProxy); + + // alice systemCoin supply burned in collateral auction + assertEq(systemCoin.totalSupply(), initialSystemCoinSupply - MINT); + + // bob's non-deposited collateral balance after collateral auction + uint256 _externalCollateralGain = collateral[TKN].balanceOf(bob) - _externalCollateralBalanceBob; + emit log_named_uint('_externalCollateralGain -------', _externalCollateralGain); + + // coinBalance of accountingEngine: +100 ether + _logWadAccountingEngineCoinAndDebtBalance(); + + // CAH still holds 60 ether of collateral after auction, because more collateral needs to be sold + _logWadCollateralAuctionHouseTokenCollateral(TKN); + assertEq(safeEngine.tokenCollateral(TKN, address(collateralAuctionHouse[TKN])), DEPOSIT - _externalCollateralGain); + + // alice's tokenCollateral balance after the auction the initial deposit minus the auctioned collateral + assertEq(safeEngine.tokenCollateral(TKN, aliceNFV.safeHandler), 0); + } + + // HELPER FUNCTIONS + + function _collateralDevaluation(bytes32 _cType, uint256 _devaluation) internal { + uint256 _p = delayedOracle[_cType].read(); + DelayedOracleForTest(address(delayedOracle[_cType])).setPriceAndValidity(_p - _devaluation, true); + oracleRelayer.updateCollateralPrice(_cType); + _refreshCData(_cType); + } + + function _refreshCData(bytes32 _cType) internal { + cTypeData = safeEngine.cData(_cType); + liquidationPrice = cTypeData.liquidationPrice; + accumulatedRate = cTypeData.accumulatedRate; + + oracleParams = oracleRelayer.cParams(_cType); + liquidationCRatio = oracleParams.liquidationCRatio; + safetyCRatio = oracleParams.safetyCRatio; + } + + function _userVaultSetup( + bytes32 _cType, + address _user, + uint256 _amount, + string memory _name + ) internal returns (address _proxy) { + _proxy = _deployOrFind(_user); + _mintToken(_cType, _user, _amount, _proxy); + vm.label(_proxy, _name); + vm.prank(_proxy); + vaults[_proxy] = safeManager.openSAFE(_cType, _proxy); + } + + function _mintToken(bytes32 _cType, address _account, uint256 _amount, address _okAccount) internal { + vm.startPrank(_account); + deal(address(collateral[_cType]), _account, _amount); + if (_okAccount != address(0)) { + IERC20(address(collateral[_cType])).approve(_okAccount, _amount); + } + vm.stopPrank(); + } + + function _deployOrFind(address _owner) internal returns (address) { + address proxy = vault721.getProxy(_owner); + if (proxy == address(0)) { + return address(vault721.build(_owner)); + } else { + return proxy; + } + } + + function _depositCollateralAndGenDebt( + bytes32 _cType, + uint256 _safeId, + uint256 _collatAmount, + uint256 _deltaWad, + address _proxy + ) internal { + vm.startPrank(ODProxy(_proxy).OWNER()); + bytes memory _payload = abi.encodeWithSelector( + basicActions.lockTokenCollateralAndGenerateDebt.selector, + address(safeManager), + address(collateralJoin[_cType]), + address(coinJoin), + _safeId, + _collatAmount, + _deltaWad + ); + ODProxy(_proxy).execute(address(basicActions), _payload); + vm.stopPrank(); + } + + function _buyCollateral( + bytes32 _cType, + uint256 _auctionId, + uint256 _minCollateral, + uint256 _bid, + address _proxy + ) internal { + vm.startPrank(ODProxy(_proxy).OWNER()); + bytes memory _payload = abi.encodeWithSelector( + collateralBidActions.buyCollateral.selector, + address(coinJoin), + address(collateralJoin[_cType]), + address(collateralAuctionHouse[_cType]), + _auctionId, + _minCollateral, + _bid + ); + ODProxy(_proxy).execute(address(collateralBidActions), _payload); + vm.stopPrank(); + } + + function _logWadAccountingEngineCoinAndDebtBalance() internal { + emit log_named_uint('_accountingEngineCoinBalance --', safeEngine.coinBalance(address(accountingEngine)) / RAY); + emit log_named_uint('_accountingEngineDebtBalance --', safeEngine.debtBalance(address(accountingEngine)) / RAY); + } + + function _logWadCollateralAuctionHouseTokenCollateral(bytes32 _cType) internal { + emit log_named_uint( + '_CAH_tokenCollateral ----------', safeEngine.tokenCollateral(_cType, address(collateralAuctionHouse[_cType])) + ); + } +} From f8bf2dc11cace8a8d9b5b60421f6662bf2ac361d Mon Sep 17 00:00:00 2001 From: Hunter King Date: Tue, 2 Jul 2024 21:11:10 -0600 Subject: [PATCH 2/7] edit comment --- test/e2e/E2ELiquidation.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol index 53c52c52..b4bb462c 100644 --- a/test/e2e/E2ELiquidation.t.sol +++ b/test/e2e/E2ELiquidation.t.sol @@ -60,7 +60,7 @@ contract E2ELiquidation is Common { } function testBuyCollateral1() public { - // CAH holds all 185 ether of collateral after liquidation and before auction + // CAH holds all 136 ether of collateral after liquidation and before auction _logWadCollateralAuctionHouseTokenCollateral(TKN); assertEq(safeEngine.tokenCollateral(TKN, address(collateralAuctionHouse[TKN])), DEPOSIT); From 31edfb23aa3388415a151ab39ae670b2fe6988ae Mon Sep 17 00:00:00 2001 From: Hunter King Date: Wed, 3 Jul 2024 17:29:24 -0600 Subject: [PATCH 3/7] change test params --- test/e2e/E2ELiquidation.t.sol | 134 +++++++++++++++++----------------- test/e2e/TestParams.s.sol | 2 +- test/e2e/TestParams.t.sol | 2 +- 3 files changed, 68 insertions(+), 70 deletions(-) diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol index b4bb462c..671a9c76 100644 --- a/test/e2e/E2ELiquidation.t.sol +++ b/test/e2e/E2ELiquidation.t.sol @@ -8,13 +8,12 @@ import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol'; import {IOracleRelayer} from '@interfaces/IOracleRelayer.sol'; import {Math, RAY} from '@libraries/Math.sol'; import {DelayedOracleForTest} from '@test/mocks/DelayedOracleForTest.sol'; -import {Common, COLLAT, DEBT, TKN} from '@test/e2e/Common.t.sol'; +import {Common, COLLAT, DEBT, RETH} from '@test/e2e/Common.t.sol'; uint256 constant MINUS_0_5_PERCENT_PER_HOUR = 999_998_607_628_240_588_157_433_861; -uint256 constant DEPOSIT = 135 ether + 1; // 136% collateralized -uint256 constant MINT = 100 ether; -uint256 constant DEFAULT_DEVALUATION = 0.2 ether; -uint256 constant USER_AMOUNT = 1000 ether; +uint256 constant DEPOSIT = 0.5 ether; // $1000 worth of RETH (1 RETH = $2000) +uint256 constant MINT = 740 ether; // $740 worth of OD (135% over-collateralization) +uint256 constant USER_AMOUNT = 500 ether; contract E2ELiquidation is Common { using Math for uint256; @@ -34,76 +33,70 @@ contract E2ELiquidation is Common { address public bobProxy; uint256 public initialSystemCoinSupply; + IERC20 public reth; + mapping(address proxy => uint256 safeId) public vaults; function setUp() public virtual override { super.setUp(); - _refreshCData(TKN); + refreshCData(RETH); + reth = IERC20(address(collateral[RETH])); - aliceProxy = _userVaultSetup(TKN, alice, USER_AMOUNT, 'AliceProxy'); + aliceProxy = userVaultSetup(RETH, alice, USER_AMOUNT, 'AliceProxy'); aliceNFV = vault721.getNfvState(vaults[aliceProxy]); - _depositCollateralAndGenDebt(TKN, vaults[aliceProxy], DEPOSIT, MINT, aliceProxy); + depositCollateralAndGenDebt(RETH, vaults[aliceProxy], DEPOSIT, MINT, aliceProxy); - bobProxy = _userVaultSetup(TKN, bob, USER_AMOUNT, 'BobProxy'); + bobProxy = userVaultSetup(RETH, bob, USER_AMOUNT, 'BobProxy'); bobNFV = vault721.getNfvState(vaults[bobProxy]); - _depositCollateralAndGenDebt(TKN, vaults[bobProxy], DEPOSIT * 3, MINT * 3, bobProxy); + depositCollateralAndGenDebt(RETH, vaults[bobProxy], DEPOSIT * 3, MINT * 3, bobProxy); initialSystemCoinSupply = systemCoin.totalSupply(); - _refreshCData(TKN); - - _collateralDevaluation(TKN, DEFAULT_DEVALUATION); - auctionId = liquidationEngine.liquidateSAFE(TKN, aliceNFV.safeHandler); - vm.prank(bob); systemCoin.approve(bobProxy, USER_AMOUNT); } - function testBuyCollateral1() public { - // CAH holds all 136 ether of collateral after liquidation and before auction - _logWadCollateralAuctionHouseTokenCollateral(TKN); - assertEq(safeEngine.tokenCollateral(TKN, address(collateralAuctionHouse[TKN])), DEPOSIT); - - // alice has no collateral after liquidation - assertEq(safeEngine.tokenCollateral(TKN, aliceNFV.safeHandler), 0); - - // bob's non-deposited collateral balance before collateral auction - uint256 _externalCollateralBalanceBob = collateral[TKN].balanceOf(bob); - - // alice + bob systemCoin supply - assertEq(initialSystemCoinSupply, systemCoin.totalSupply()); - - // bob to buy alice's liquidated collateral - _buyCollateral(TKN, auctionId, 0, MINT, bobProxy); - - // alice systemCoin supply burned in collateral auction - assertEq(systemCoin.totalSupply(), initialSystemCoinSupply - MINT); - - // bob's non-deposited collateral balance after collateral auction - uint256 _externalCollateralGain = collateral[TKN].balanceOf(bob) - _externalCollateralBalanceBob; - emit log_named_uint('_externalCollateralGain -------', _externalCollateralGain); - - // coinBalance of accountingEngine: +100 ether - _logWadAccountingEngineCoinAndDebtBalance(); - - // CAH still holds 60 ether of collateral after auction, because more collateral needs to be sold - _logWadCollateralAuctionHouseTokenCollateral(TKN); - assertEq(safeEngine.tokenCollateral(TKN, address(collateralAuctionHouse[TKN])), DEPOSIT - _externalCollateralGain); - - // alice's tokenCollateral balance after the auction the initial deposit minus the auctioned collateral - assertEq(safeEngine.tokenCollateral(TKN, aliceNFV.safeHandler), 0); + function testAssumptions() public { + assertEq(reth.balanceOf(alice), USER_AMOUNT - DEPOSIT); + assertEq(reth.balanceOf(bob), USER_AMOUNT - DEPOSIT * 3); + assertEq(systemCoin.totalSupply(), 1480 ether); + readDelayedPrice(RETH); + collateralDevaluation(RETH, 200 ether); // 10% devaulation; 135% => 125% over-collateralization + readDelayedPrice(RETH); + emitRatio(RETH, aliceNFV.safeHandler); + liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); } + /** + * @dev initial setup + * RETH: $2000 + * OD = $1 + * Liquidation Penalty: 5% + * Liquidation Ratio: 125% + * Safety Ration: 135% + * + * @notice scenario + * User deposit $1000 worth of RETH (0.5 ether) and borrows $740 OD (135% ratio) + * + */ + function testLiquidation() public {} + // HELPER FUNCTIONS + function readDelayedPrice(bytes32 _cType) public returns (uint256) { + uint256 _p = delayedOracle[_cType].read(); + emit log_named_uint('CType Price Read', _p); + return _p; + } - function _collateralDevaluation(bytes32 _cType, uint256 _devaluation) internal { + function collateralDevaluation(bytes32 _cType, uint256 _devaluation) public returns (uint256) { uint256 _p = delayedOracle[_cType].read(); DelayedOracleForTest(address(delayedOracle[_cType])).setPriceAndValidity(_p - _devaluation, true); oracleRelayer.updateCollateralPrice(_cType); - _refreshCData(_cType); + refreshCData(_cType); + return delayedOracle[_cType].read(); } - function _refreshCData(bytes32 _cType) internal { + function refreshCData(bytes32 _cType) public { cTypeData = safeEngine.cData(_cType); liquidationPrice = cTypeData.liquidationPrice; accumulatedRate = cTypeData.accumulatedRate; @@ -113,20 +106,20 @@ contract E2ELiquidation is Common { safetyCRatio = oracleParams.safetyCRatio; } - function _userVaultSetup( + function userVaultSetup( bytes32 _cType, address _user, uint256 _amount, string memory _name - ) internal returns (address _proxy) { - _proxy = _deployOrFind(_user); - _mintToken(_cType, _user, _amount, _proxy); + ) public returns (address _proxy) { + _proxy = deployOrFind(_user); + mintToken(_cType, _user, _amount, _proxy); vm.label(_proxy, _name); vm.prank(_proxy); vaults[_proxy] = safeManager.openSAFE(_cType, _proxy); } - function _mintToken(bytes32 _cType, address _account, uint256 _amount, address _okAccount) internal { + function mintToken(bytes32 _cType, address _account, uint256 _amount, address _okAccount) public { vm.startPrank(_account); deal(address(collateral[_cType]), _account, _amount); if (_okAccount != address(0)) { @@ -135,7 +128,7 @@ contract E2ELiquidation is Common { vm.stopPrank(); } - function _deployOrFind(address _owner) internal returns (address) { + function deployOrFind(address _owner) public returns (address) { address proxy = vault721.getProxy(_owner); if (proxy == address(0)) { return address(vault721.build(_owner)); @@ -144,13 +137,13 @@ contract E2ELiquidation is Common { } } - function _depositCollateralAndGenDebt( + function depositCollateralAndGenDebt( bytes32 _cType, uint256 _safeId, uint256 _collatAmount, uint256 _deltaWad, address _proxy - ) internal { + ) public { vm.startPrank(ODProxy(_proxy).OWNER()); bytes memory _payload = abi.encodeWithSelector( basicActions.lockTokenCollateralAndGenerateDebt.selector, @@ -165,13 +158,13 @@ contract E2ELiquidation is Common { vm.stopPrank(); } - function _buyCollateral( + function buyCollateral( bytes32 _cType, uint256 _auctionId, uint256 _minCollateral, uint256 _bid, address _proxy - ) internal { + ) public { vm.startPrank(ODProxy(_proxy).OWNER()); bytes memory _payload = abi.encodeWithSelector( collateralBidActions.buyCollateral.selector, @@ -186,14 +179,19 @@ contract E2ELiquidation is Common { vm.stopPrank(); } - function _logWadAccountingEngineCoinAndDebtBalance() internal { - emit log_named_uint('_accountingEngineCoinBalance --', safeEngine.coinBalance(address(accountingEngine)) / RAY); - emit log_named_uint('_accountingEngineDebtBalance --', safeEngine.debtBalance(address(accountingEngine)) / RAY); + function getSAFE(bytes32 _cType, address _safe) public view returns (uint256 _collateral, uint256 _debt) { + ISAFEEngine.SAFE memory _safeData = safeEngine.safes(_cType, _safe); + _collateral = _safeData.lockedCollateral; + _debt = _safeData.generatedDebt; } - function _logWadCollateralAuctionHouseTokenCollateral(bytes32 _cType) internal { - emit log_named_uint( - '_CAH_tokenCollateral ----------', safeEngine.tokenCollateral(_cType, address(collateralAuctionHouse[_cType])) - ); + function getRatio(bytes32 _cType, uint256 _collateral, uint256 _debt) public view returns (uint256 _ratio) { + _ratio = _collateral.wmul(oracleRelayer.cParams(_cType).oracle.read()).wdiv(_debt.wmul(accumulatedRate)); + } + + function emitRatio(bytes32 _cType, address _safe) public returns (uint256 _ratio) { + (uint256 _collateral, uint256 _debt) = getSAFE(_cType, _safe); + _ratio = getRatio(_cType, _collateral, _debt); + emit log_named_uint('CType to Debt Ratio', _ratio); } } diff --git a/test/e2e/TestParams.s.sol b/test/e2e/TestParams.s.sol index 0a4c1716..2f8ef9e6 100644 --- a/test/e2e/TestParams.s.sol +++ b/test/e2e/TestParams.s.sol @@ -117,7 +117,7 @@ abstract contract TestParams is Contracts, Params { _liquidationEngineCParams[_cType] = ILiquidationEngine.LiquidationEngineCollateralParams({ collateralAuctionHouse: address(collateralAuctionHouse[_cType]), - liquidationPenalty: 1.1e18, // WAD + liquidationPenalty: 1.05e18, // WAD liquidationQuantity: 100_000e45 // RAD }); diff --git a/test/e2e/TestParams.t.sol b/test/e2e/TestParams.t.sol index 8f1026a0..cb106308 100644 --- a/test/e2e/TestParams.t.sol +++ b/test/e2e/TestParams.t.sol @@ -117,7 +117,7 @@ abstract contract TestParams is Contracts, Params { _liquidationEngineCParams[_cType] = ILiquidationEngine.LiquidationEngineCollateralParams({ collateralAuctionHouse: address(collateralAuctionHouse[_cType]), - liquidationPenalty: 1.1e18, // WAD + liquidationPenalty: 1.05e18, // WAD liquidationQuantity: 100_000e45 // RAD }); From b30ce3314c463410c909ed3b72e3950261ac3bf0 Mon Sep 17 00:00:00 2001 From: Hunter King Date: Wed, 3 Jul 2024 19:53:07 -0600 Subject: [PATCH 4/7] add liquidation tests --- test/e2e/E2ELiquidation.t.sol | 138 ++++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol index 671a9c76..a67a1b4e 100644 --- a/test/e2e/E2ELiquidation.t.sol +++ b/test/e2e/E2ELiquidation.t.sol @@ -5,6 +5,7 @@ import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; import {ODProxy} from '@contracts/proxies/ODProxy.sol'; import {IVault721} from '@interfaces/proxies/IVault721.sol'; import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol'; +import {ILiquidationEngine} from '@interfaces/ILiquidationEngine.sol'; import {IOracleRelayer} from '@interfaces/IOracleRelayer.sol'; import {Math, RAY} from '@libraries/Math.sol'; import {DelayedOracleForTest} from '@test/mocks/DelayedOracleForTest.sol'; @@ -14,6 +15,11 @@ uint256 constant MINUS_0_5_PERCENT_PER_HOUR = 999_998_607_628_240_588_157_433_86 uint256 constant DEPOSIT = 0.5 ether; // $1000 worth of RETH (1 RETH = $2000) uint256 constant MINT = 740 ether; // $740 worth of OD (135% over-collateralization) uint256 constant USER_AMOUNT = 500 ether; +uint256 constant ELEVEN_PERCENT = 160 ether; +uint256 constant FIFTEEN_PERCENT = 218 ether; +uint256 constant TWENTY_PERCENT = 290 ether; +uint256 constant THIRTY_PERCENT = 436 ether; +uint256 constant FORTY_PERCENT = 582 ether; contract E2ELiquidation is Common { using Math for uint256; @@ -25,6 +31,7 @@ contract E2ELiquidation is Common { ISAFEEngine.SAFEEngineCollateralData public cTypeData; IOracleRelayer.OracleRelayerCollateralParams public oracleParams; + ILiquidationEngine.LiquidationEngineCollateralParams public cTypeParams; IVault721.NFVState public aliceNFV; IVault721.NFVState public bobNFV; @@ -42,6 +49,8 @@ contract E2ELiquidation is Common { refreshCData(RETH); reth = IERC20(address(collateral[RETH])); + cTypeParams = liquidationEngine.cParams(RETH); + aliceProxy = userVaultSetup(RETH, alice, USER_AMOUNT, 'AliceProxy'); aliceNFV = vault721.getNfvState(vaults[aliceProxy]); depositCollateralAndGenDebt(RETH, vaults[aliceProxy], DEPOSIT, MINT, aliceProxy); @@ -53,18 +62,20 @@ contract E2ELiquidation is Common { initialSystemCoinSupply = systemCoin.totalSupply(); vm.prank(bob); - systemCoin.approve(bobProxy, USER_AMOUNT); + systemCoin.approve(bobProxy, type(uint256).max); } function testAssumptions() public { assertEq(reth.balanceOf(alice), USER_AMOUNT - DEPOSIT); assertEq(reth.balanceOf(bob), USER_AMOUNT - DEPOSIT * 3); - assertEq(systemCoin.totalSupply(), 1480 ether); + assertEq(systemCoin.totalSupply(), 2960 ether); + emitRatio(RETH, aliceNFV.safeHandler); // 135% over-collateralized readDelayedPrice(RETH); - collateralDevaluation(RETH, 200 ether); // 10% devaulation; 135% => 125% over-collateralization + collateralDevaluation(RETH, ELEVEN_PERCENT); // 11% devaulation readDelayedPrice(RETH); - emitRatio(RETH, aliceNFV.safeHandler); + emitRatio(RETH, aliceNFV.safeHandler); // 124% over-collateralized liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + emit log_named_uint('Liquidation Penalty', cTypeParams.liquidationPenalty); } /** @@ -79,9 +90,110 @@ contract E2ELiquidation is Common { * User deposit $1000 worth of RETH (0.5 ether) and borrows $740 OD (135% ratio) * */ - function testLiquidation() public {} + function testLiquidation1() public { + emitInternalAndExternalCollateralAndDebt(); + + collateralDevaluation(RETH, ELEVEN_PERCENT); + emitRatio(RETH, aliceNFV.safeHandler); + auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + + emitInternalAndExternalCollateralAndDebt(); + + vm.prank(bob); + buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); + + emitInternalAndExternalCollateralAndDebt(); + } + + function testLiquidation2() public { + emitInternalAndExternalCollateralAndDebt(); + + collateralDevaluation(RETH, FIFTEEN_PERCENT); + emitRatio(RETH, aliceNFV.safeHandler); + auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + + emitInternalAndExternalCollateralAndDebt(); + + vm.prank(bob); + buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); + + emitInternalAndExternalCollateralAndDebt(); + } + + function testLiquidation3() public { + emitInternalAndExternalCollateralAndDebt(); + + collateralDevaluation(RETH, TWENTY_PERCENT); + emitRatio(RETH, aliceNFV.safeHandler); + auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + + emitInternalAndExternalCollateralAndDebt(); + + vm.prank(bob); + buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); + + emitInternalAndExternalCollateralAndDebt(); + } + + function testLiquidation4() public { + emitInternalAndExternalCollateralAndDebt(); + + collateralDevaluation(RETH, THIRTY_PERCENT); + emitRatio(RETH, aliceNFV.safeHandler); + auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + + emitInternalAndExternalCollateralAndDebt(); + + vm.prank(bob); + buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); + + emitInternalAndExternalCollateralAndDebt(); + } + + function testLiquidation5() public { + emitInternalAndExternalCollateralAndDebt(); + + collateralDevaluation(RETH, FORTY_PERCENT); + emitRatio(RETH, aliceNFV.safeHandler); + auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + + emitInternalAndExternalCollateralAndDebt(); + + vm.prank(bob); + buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); + + emitInternalAndExternalCollateralAndDebt(); + } // HELPER FUNCTIONS + function emitInternalAndExternalCollateralAndDebt() public { + emit log_named_uint('CAH System Coin Bal', systemCoin.balanceOf(address(collateralAuctionHouse[RETH]))); + emit log_named_uint( + 'CAH Internal cType Bal', safeEngine.tokenCollateral(RETH, address(collateralAuctionHouse[RETH])) + ); + emit log_named_uint('Ali Internal cType Bal', safeEngine.tokenCollateral(RETH, aliceNFV.safeHandler)); + (uint256 _c, uint256 _d) = getSAFE(RETH, aliceNFV.safeHandler); + emit log_named_uint('Ali Locked cType Bal', _c); + emit log_named_uint('Ali System Coin Bal', systemCoin.balanceOf(alice)); + emit log_named_uint('Ali Generate Debt Bal', _d); + } + + function getSAFE(bytes32 _cType, address _safe) public view returns (uint256 _collateral, uint256 _debt) { + ISAFEEngine.SAFE memory _safeData = safeEngine.safes(_cType, _safe); + _collateral = _safeData.lockedCollateral; + _debt = _safeData.generatedDebt; + } + + function getRatio(bytes32 _cType, uint256 _collateral, uint256 _debt) public view returns (uint256 _ratio) { + _ratio = _collateral.wmul(oracleRelayer.cParams(_cType).oracle.read()).wdiv(_debt.wmul(accumulatedRate)); + } + + function emitRatio(bytes32 _cType, address _safe) public returns (uint256 _ratio) { + (uint256 _collateral, uint256 _debt) = getSAFE(_cType, _safe); + _ratio = getRatio(_cType, _collateral, _debt); + emit log_named_uint('CType to Debt Ratio', _ratio / 1e7); + } + function readDelayedPrice(bytes32 _cType) public returns (uint256) { uint256 _p = delayedOracle[_cType].read(); emit log_named_uint('CType Price Read', _p); @@ -178,20 +290,4 @@ contract E2ELiquidation is Common { ODProxy(_proxy).execute(address(collateralBidActions), _payload); vm.stopPrank(); } - - function getSAFE(bytes32 _cType, address _safe) public view returns (uint256 _collateral, uint256 _debt) { - ISAFEEngine.SAFE memory _safeData = safeEngine.safes(_cType, _safe); - _collateral = _safeData.lockedCollateral; - _debt = _safeData.generatedDebt; - } - - function getRatio(bytes32 _cType, uint256 _collateral, uint256 _debt) public view returns (uint256 _ratio) { - _ratio = _collateral.wmul(oracleRelayer.cParams(_cType).oracle.read()).wdiv(_debt.wmul(accumulatedRate)); - } - - function emitRatio(bytes32 _cType, address _safe) public returns (uint256 _ratio) { - (uint256 _collateral, uint256 _debt) = getSAFE(_cType, _safe); - _ratio = getRatio(_cType, _collateral, _debt); - emit log_named_uint('CType to Debt Ratio', _ratio); - } } From 3a0d4160c14f4d2127d9842599cc960b49f8714a Mon Sep 17 00:00:00 2001 From: Hunter King Date: Fri, 5 Jul 2024 15:37:10 -0600 Subject: [PATCH 5/7] add more details about liquidation --- test/e2e/E2ELiquidation.t.sol | 118 +++++++++++++++++----------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol index a67a1b4e..de9aaf36 100644 --- a/test/e2e/E2ELiquidation.t.sol +++ b/test/e2e/E2ELiquidation.t.sol @@ -91,91 +91,93 @@ contract E2ELiquidation is Common { * */ function testLiquidation1() public { - emitInternalAndExternalCollateralAndDebt(); - - collateralDevaluation(RETH, ELEVEN_PERCENT); - emitRatio(RETH, aliceNFV.safeHandler); - auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); - - emitInternalAndExternalCollateralAndDebt(); - - vm.prank(bob); - buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); - - emitInternalAndExternalCollateralAndDebt(); + _percentLiquidation(ELEVEN_PERCENT); } function testLiquidation2() public { - emitInternalAndExternalCollateralAndDebt(); - - collateralDevaluation(RETH, FIFTEEN_PERCENT); - emitRatio(RETH, aliceNFV.safeHandler); - auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); - - emitInternalAndExternalCollateralAndDebt(); - - vm.prank(bob); - buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); - - emitInternalAndExternalCollateralAndDebt(); + _percentLiquidation(FIFTEEN_PERCENT); } function testLiquidation3() public { - emitInternalAndExternalCollateralAndDebt(); - - collateralDevaluation(RETH, TWENTY_PERCENT); - emitRatio(RETH, aliceNFV.safeHandler); - auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); - - emitInternalAndExternalCollateralAndDebt(); - - vm.prank(bob); - buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); - - emitInternalAndExternalCollateralAndDebt(); + _percentLiquidation(TWENTY_PERCENT); } function testLiquidation4() public { - emitInternalAndExternalCollateralAndDebt(); - - collateralDevaluation(RETH, THIRTY_PERCENT); - emitRatio(RETH, aliceNFV.safeHandler); - auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); - - emitInternalAndExternalCollateralAndDebt(); - - vm.prank(bob); - buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); - - emitInternalAndExternalCollateralAndDebt(); + _percentLiquidation(THIRTY_PERCENT); } function testLiquidation5() public { + _percentLiquidation(FORTY_PERCENT); + } + + function _percentLiquidation(uint256 _percent) public { + // read initial values + emitRatio(RETH, aliceNFV.safeHandler); emitInternalAndExternalCollateralAndDebt(); - collateralDevaluation(RETH, FORTY_PERCENT); + // generate amoutToRaise in case of liquidation + uint256 _amountToRaise = _emitMathAndReturnAmountToRaise(); + + // devalue cType to below the liquidation ratio + collateralDevaluation(RETH, _percent); emitRatio(RETH, aliceNFV.safeHandler); - auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); + // liquidate safe + auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler); emitInternalAndExternalCollateralAndDebt(); - vm.prank(bob); - buyCollateral(RETH, auctionId, 0, 1000 ether, bobProxy); + vm.startPrank(bob); + /// bob to buy half of auction @notice token kept in CAH + buyCollateral(RETH, auctionId, 0, _amountToRaise / 2, bobProxy); + emitInternalAndExternalCollateralAndDebt(); + /// bob to buy remaining half of auction @notice token returned to Alice safe + buyCollateral(RETH, auctionId, 0, _amountToRaise / 2, bobProxy); emitInternalAndExternalCollateralAndDebt(); } + function _emitMathAndReturnAmountToRaise() public returns (uint256) { + ILiquidationEngine.LiquidationEngineCollateralParams memory _rethParams = liquidationEngine.cParams(RETH); + ILiquidationEngine.LiquidationEngineParams memory _liqParams = liquidationEngine.params(); + + (uint256 _lc, uint256 _gd) = getSAFE(RETH, aliceNFV.safeHandler); + uint256 _lq = _rethParams.liquidationQuantity; + uint256 _ad = + (_liqParams.onAuctionSystemCoinLimit - liquidationEngine.currentOnAuctionSystemCoins()).wdiv(accumulatedRate); + uint256 _limitAdjustedDebt = Math.min(_gd, Math.min(_lq, _ad)); + // emit log_named_uint('Generated Debt ', _gd); + // emit log_named_uint('Liquidation Quantity ', _lq); + // emit log_named_uint('Adjusted Debt ', _ad); + emit log_named_uint('* Limit Adjusted Debt ', _limitAdjustedDebt); + + uint256 _lcXlad = _lc * _limitAdjustedDebt / _gd; + uint256 _collateralToSell = Math.min(_lc, _lcXlad); + // emit log_named_uint('Locked Collateral ', _lc); + // emit log_named_uint('Locked C X LimAdjDebt', _lcXlad); + emit log_named_uint('* Collateral To Sell ', _collateralToSell); + + uint256 _amountToRaise = (_limitAdjustedDebt * accumulatedRate).wmul(_rethParams.liquidationPenalty) / 1e27; + emit log_named_uint('* Amount To Raise ', _amountToRaise); + return _amountToRaise; + } + // HELPER FUNCTIONS function emitInternalAndExternalCollateralAndDebt() public { - emit log_named_uint('CAH System Coin Bal', systemCoin.balanceOf(address(collateralAuctionHouse[RETH]))); + emit log_named_uint('CAH System Coin Bal', systemCoin.balanceOf(address(collateralAuctionHouse[RETH]))); emit log_named_uint( 'CAH Internal cType Bal', safeEngine.tokenCollateral(RETH, address(collateralAuctionHouse[RETH])) ); - emit log_named_uint('Ali Internal cType Bal', safeEngine.tokenCollateral(RETH, aliceNFV.safeHandler)); - (uint256 _c, uint256 _d) = getSAFE(RETH, aliceNFV.safeHandler); - emit log_named_uint('Ali Locked cType Bal', _c); - emit log_named_uint('Ali System Coin Bal', systemCoin.balanceOf(alice)); - emit log_named_uint('Ali Generate Debt Bal', _d); + emit log_named_uint('Ali Internal CType Bal', safeEngine.tokenCollateral(RETH, aliceNFV.safeHandler)); + (uint256 _a_c, uint256 _a_d) = getSAFE(RETH, aliceNFV.safeHandler); + emit log_named_uint('Ali Locked CType Bal', _a_c); + emit log_named_uint('Ali System Coin Bal', systemCoin.balanceOf(alice)); + emit log_named_uint('Ali Generate Debt Bal', _a_d); + // emit log_named_uint('Bob Internal cType Bal', safeEngine.tokenCollateral(RETH, bobNFV.safeHandler)); + // (uint256 _b_c, uint256 _b_d) = getSAFE(RETH, bobNFV.safeHandler); + // emit log_named_uint('Bob Locked cType Bal', _b_c); + // emit log_named_uint('Bob System Coin Bal', systemCoin.balanceOf(bob)); + // emit log_named_uint('Bob Generate Debt Bal', _b_d); + emit log_named_bytes32('BREAK ----------------', bytes32(0x00)); } function getSAFE(bytes32 _cType, address _safe) public view returns (uint256 _collateral, uint256 _debt) { From 482434aa5807b9bf8645132d2b2a8ecb46164af7 Mon Sep 17 00:00:00 2001 From: Hunter King Date: Mon, 8 Jul 2024 12:04:28 -0600 Subject: [PATCH 6/7] format emits --- test/e2e/E2ELiquidation.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol index de9aaf36..063853d9 100644 --- a/test/e2e/E2ELiquidation.t.sol +++ b/test/e2e/E2ELiquidation.t.sol @@ -198,7 +198,7 @@ contract E2ELiquidation is Common { function readDelayedPrice(bytes32 _cType) public returns (uint256) { uint256 _p = delayedOracle[_cType].read(); - emit log_named_uint('CType Price Read', _p); + emit log_named_uint('CType Price Read', _p); return _p; } From 478d50b1686c102ac0be37a961373927a93dc1d2 Mon Sep 17 00:00:00 2001 From: Hunter King Date: Mon, 8 Jul 2024 21:24:56 -0600 Subject: [PATCH 7/7] add OD fix --- script/Common.s.sol | 15 +++++++++++++-- test/e2e/E2ELiquidation.t.sol | 1 - 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/script/Common.s.sol b/script/Common.s.sol index 078ad596..01797051 100644 --- a/script/Common.s.sol +++ b/script/Common.s.sol @@ -6,6 +6,7 @@ import '@script/Registry.s.sol'; import {Test} from 'forge-std/Test.sol'; import {VmSafe} from 'forge-std/Script.sol'; import {Params, ParamChecker, OD, ETH_A, ARB, JOB_REWARD} from '@script/Params.s.sol'; +import {AuthorizableUpgradeable} from '@contracts/utils/AuthorizableUpgradeable.sol'; import 'forge-std/console.sol'; abstract contract Common is Contracts, Params, Test { @@ -215,12 +216,22 @@ abstract contract Common is Contracts, Params, Test { if (!isNetworkAnvil()) { address systemCoinAddress = create2.create2deploy(_systemCoinSalt, _systemCoinInitCode); systemCoin = ISystemCoin(systemCoinAddress); + systemCoin.initialize('Open Dollar', 'OD'); } else { - systemCoin = new OpenDollar(); + if (_isTest && !isNetworkArbitrumSepolia()) { + systemCoin = OpenDollar(0x221A0f68770658C15B525d0F89F5da2baAB5f321); + vm.stopPrank(); + vm.startPrank(AuthorizableUpgradeable(address(systemCoin)).authorizedAccounts()[0]); + AuthorizableUpgradeable(address(systemCoin)).addAuthorization(deployer); + vm.stopPrank(); + vm.startPrank(deployer); + } else { + systemCoin = new OpenDollar(); + systemCoin.initialize('Open Dollar', 'OD'); + } protocolToken = new OpenDollarGovernance(); protocolToken.initialize('Open Dollar Governance', 'ODG'); } - systemCoin.initialize('Open Dollar', 'OD'); address[] memory members = new address[](0); if (isNetworkAnvil()) { diff --git a/test/e2e/E2ELiquidation.t.sol b/test/e2e/E2ELiquidation.t.sol index 063853d9..e5ebbcf2 100644 --- a/test/e2e/E2ELiquidation.t.sol +++ b/test/e2e/E2ELiquidation.t.sol @@ -68,7 +68,6 @@ contract E2ELiquidation is Common { function testAssumptions() public { assertEq(reth.balanceOf(alice), USER_AMOUNT - DEPOSIT); assertEq(reth.balanceOf(bob), USER_AMOUNT - DEPOSIT * 3); - assertEq(systemCoin.totalSupply(), 2960 ether); emitRatio(RETH, aliceNFV.safeHandler); // 135% over-collateralized readDelayedPrice(RETH); collateralDevaluation(RETH, ELEVEN_PERCENT); // 11% devaulation