Skip to content

Commit

Permalink
Merge pull request #731 from open-dollar/liq-test
Browse files Browse the repository at this point in the history
Liquidation test
  • Loading branch information
daopunk authored Jul 9, 2024
2 parents d805d33 + 478d50b commit 984c17c
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 4 deletions.
15 changes: 13 additions & 2 deletions script/Common.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()) {
Expand Down
294 changes: 294 additions & 0 deletions test/e2e/E2ELiquidation.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
// 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 {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';
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 = 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;

uint256 public liquidationCRatio; // RAY
uint256 public safetyCRatio; // RAY
uint256 public accumulatedRate; // RAY
uint256 public liquidationPrice; // RAY

ISAFEEngine.SAFEEngineCollateralData public cTypeData;
IOracleRelayer.OracleRelayerCollateralParams public oracleParams;
ILiquidationEngine.LiquidationEngineCollateralParams public cTypeParams;

IVault721.NFVState public aliceNFV;
IVault721.NFVState public bobNFV;

address public aliceProxy;
address public bobProxy;
uint256 public initialSystemCoinSupply;

IERC20 public reth;

mapping(address proxy => uint256 safeId) public vaults;

function setUp() public virtual override {
super.setUp();
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);

bobProxy = userVaultSetup(RETH, bob, USER_AMOUNT, 'BobProxy');
bobNFV = vault721.getNfvState(vaults[bobProxy]);
depositCollateralAndGenDebt(RETH, vaults[bobProxy], DEPOSIT * 3, MINT * 3, bobProxy);

initialSystemCoinSupply = systemCoin.totalSupply();

vm.prank(bob);
systemCoin.approve(bobProxy, type(uint256).max);
}

function testAssumptions() public {
assertEq(reth.balanceOf(alice), USER_AMOUNT - DEPOSIT);
assertEq(reth.balanceOf(bob), USER_AMOUNT - DEPOSIT * 3);
emitRatio(RETH, aliceNFV.safeHandler); // 135% over-collateralized
readDelayedPrice(RETH);
collateralDevaluation(RETH, ELEVEN_PERCENT); // 11% devaulation
readDelayedPrice(RETH);
emitRatio(RETH, aliceNFV.safeHandler); // 124% over-collateralized
liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler);
emit log_named_uint('Liquidation Penalty', cTypeParams.liquidationPenalty);
}

/**
* @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 testLiquidation1() public {
_percentLiquidation(ELEVEN_PERCENT);
}

function testLiquidation2() public {
_percentLiquidation(FIFTEEN_PERCENT);
}

function testLiquidation3() public {
_percentLiquidation(TWENTY_PERCENT);
}

function testLiquidation4() public {
_percentLiquidation(THIRTY_PERCENT);
}

function testLiquidation5() public {
_percentLiquidation(FORTY_PERCENT);
}

function _percentLiquidation(uint256 _percent) public {
// read initial values
emitRatio(RETH, aliceNFV.safeHandler);
emitInternalAndExternalCollateralAndDebt();

// generate amoutToRaise in case of liquidation
uint256 _amountToRaise = _emitMathAndReturnAmountToRaise();

// devalue cType to below the liquidation ratio
collateralDevaluation(RETH, _percent);
emitRatio(RETH, aliceNFV.safeHandler);

// liquidate safe
auctionId = liquidationEngine.liquidateSAFE(RETH, aliceNFV.safeHandler);
emitInternalAndExternalCollateralAndDebt();

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 Internal cType Bal', safeEngine.tokenCollateral(RETH, address(collateralAuctionHouse[RETH]))
);
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) {
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);
return _p;
}

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);
return delayedOracle[_cType].read();
}

function refreshCData(bytes32 _cType) public {
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
) 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) public {
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) public 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
) public {
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
) public {
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();
}
}
2 changes: 1 addition & 1 deletion test/e2e/TestParams.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
});

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/TestParams.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
});

Expand Down

0 comments on commit 984c17c

Please sign in to comment.