forked from Synthetixio/synthetix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLiquidations.sol
251 lines (195 loc) · 9.51 KB
/
Liquidations.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./interfaces/ILiquidations.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./EternalStorage.sol";
import "./interfaces/ISynthetix.sol";
import "./interfaces/IExchangeRates.sol";
import "./interfaces/IIssuer.sol";
import "./interfaces/ISystemStatus.sol";
// https://docs.synthetix.io/contracts/source/contracts/liquidations
contract Liquidations is Owned, MixinSystemSettings, ILiquidations {
using SafeMath for uint;
using SafeDecimalMath for uint;
struct LiquidationEntry {
uint deadline;
address caller;
}
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_ETERNALSTORAGE_LIQUIDATIONS = "EternalStorageLiquidations";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
/* ========== CONSTANTS ========== */
// Storage keys
bytes32 public constant LIQUIDATION_DEADLINE = "LiquidationDeadline";
bytes32 public constant LIQUIDATION_CALLER = "LiquidationCaller";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](5);
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
newAddresses[1] = CONTRACT_SYNTHETIX;
newAddresses[2] = CONTRACT_ETERNALSTORAGE_LIQUIDATIONS;
newAddresses[3] = CONTRACT_ISSUER;
newAddresses[4] = CONTRACT_EXRATES;
addresses = combineArrays(existingAddresses, newAddresses);
}
function synthetix() internal view returns (ISynthetix) {
return ISynthetix(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
// refactor to synthetix storage eternal storage contract once that's ready
function eternalStorageLiquidations() internal view returns (EternalStorage) {
return EternalStorage(requireAndGetAddress(CONTRACT_ETERNALSTORAGE_LIQUIDATIONS));
}
function issuanceRatio() external view returns (uint) {
return getIssuanceRatio();
}
function liquidationDelay() external view returns (uint) {
return getLiquidationDelay();
}
function liquidationRatio() external view returns (uint) {
return getLiquidationRatio();
}
function liquidationPenalty() external view returns (uint) {
return getLiquidationPenalty();
}
function liquidationCollateralRatio() external view returns (uint) {
return SafeDecimalMath.unit().divideDecimalRound(getLiquidationRatio());
}
function getLiquidationDeadlineForAccount(address account) external view returns (uint) {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
return liquidation.deadline;
}
function isOpenForLiquidation(address account) external view returns (bool) {
uint accountCollateralisationRatio = synthetix().collateralisationRatio(account);
// Liquidation closed if collateral ratio less than or equal target issuance Ratio
// Account with no snx collateral will also not be open for liquidation (ratio is 0)
if (accountCollateralisationRatio <= getIssuanceRatio()) {
return false;
}
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
// liquidation cap at issuanceRatio is checked above
if (_deadlinePassed(liquidation.deadline)) {
return true;
}
return false;
}
function isLiquidationDeadlinePassed(address account) external view returns (bool) {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
return _deadlinePassed(liquidation.deadline);
}
function _deadlinePassed(uint deadline) internal view returns (bool) {
// check deadline is set > 0
// check now > deadline
return deadline > 0 && now > deadline;
}
/**
* r = target issuance ratio
* D = debt balance
* V = Collateral
* P = liquidation penalty
* Calculates amount of synths = (D - V * r) / (1 - (1 + P) * r)
*/
function calculateAmountToFixCollateral(uint debtBalance, uint collateral) external view returns (uint) {
uint ratio = getIssuanceRatio();
uint unit = SafeDecimalMath.unit();
uint dividend = debtBalance.sub(collateral.multiplyDecimal(ratio));
uint divisor = unit.sub(unit.add(getLiquidationPenalty()).multiplyDecimal(ratio));
return dividend.divideDecimal(divisor);
}
// get liquidationEntry for account
// returns deadline = 0 when not set
function _getLiquidationEntryForAccount(address account) internal view returns (LiquidationEntry memory _liquidation) {
_liquidation.deadline = eternalStorageLiquidations().getUIntValue(_getKey(LIQUIDATION_DEADLINE, account));
// liquidation caller not used
_liquidation.caller = address(0);
}
function _getKey(bytes32 _scope, address _account) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_scope, _account));
}
/* ========== MUTATIVE FUNCTIONS ========== */
// totalIssuedSynths checks synths for staleness
// check snx rate is not stale
function flagAccountForLiquidation(address account) external rateNotInvalid("SNX") {
systemStatus().requireSystemActive();
require(getLiquidationRatio() > 0, "Liquidation ratio not set");
require(getLiquidationDelay() > 0, "Liquidation delay not set");
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
require(liquidation.deadline == 0, "Account already flagged for liquidation");
uint accountsCollateralisationRatio = synthetix().collateralisationRatio(account);
// if accounts issuance ratio is greater than or equal to liquidation ratio set liquidation entry
require(
accountsCollateralisationRatio >= getLiquidationRatio(),
"Account issuance ratio is less than liquidation ratio"
);
uint deadline = now.add(getLiquidationDelay());
_storeLiquidationEntry(account, deadline, msg.sender);
emit AccountFlaggedForLiquidation(account, deadline);
}
// Internal function to remove account from liquidations
// Does not check collateral ratio is fixed
function removeAccountInLiquidation(address account) external onlyIssuer {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
if (liquidation.deadline > 0) {
_removeLiquidationEntry(account);
}
}
// Public function to allow an account to remove from liquidations
// Checks collateral ratio is fixed - below target issuance ratio
// Check SNX rate is not stale
function checkAndRemoveAccountInLiquidation(address account) external rateNotInvalid("SNX") {
systemStatus().requireSystemActive();
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
require(liquidation.deadline > 0, "Account has no liquidation set");
uint accountsCollateralisationRatio = synthetix().collateralisationRatio(account);
// Remove from liquidations if accountsCollateralisationRatio is fixed (less than equal target issuance ratio)
if (accountsCollateralisationRatio <= getIssuanceRatio()) {
_removeLiquidationEntry(account);
}
}
function _storeLiquidationEntry(
address _account,
uint _deadline,
address _caller
) internal {
// record liquidation deadline
eternalStorageLiquidations().setUIntValue(_getKey(LIQUIDATION_DEADLINE, _account), _deadline);
eternalStorageLiquidations().setAddressValue(_getKey(LIQUIDATION_CALLER, _account), _caller);
}
function _removeLiquidationEntry(address _account) internal {
// delete liquidation deadline
eternalStorageLiquidations().deleteUIntValue(_getKey(LIQUIDATION_DEADLINE, _account));
// delete liquidation caller
eternalStorageLiquidations().deleteAddressValue(_getKey(LIQUIDATION_CALLER, _account));
emit AccountRemovedFromLiquidation(_account, now);
}
/* ========== MODIFIERS ========== */
modifier onlyIssuer() {
require(msg.sender == address(issuer()), "Liquidations: Only the Issuer contract can perform this action");
_;
}
modifier rateNotInvalid(bytes32 currencyKey) {
require(!exchangeRates().rateIsInvalid(currencyKey), "Rate invalid or not a synth");
_;
}
/* ========== EVENTS ========== */
event AccountFlaggedForLiquidation(address indexed account, uint deadline);
event AccountRemovedFromLiquidation(address indexed account, uint time);
}