-
Notifications
You must be signed in to change notification settings - Fork 8
/
SAFEEngine.sol
439 lines (362 loc) · 17.2 KB
/
SAFEEngine.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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;
/*
Coded for Reflexer and The Money God with 🥕 by
░██╗░░░░░░░██╗░█████╗░███╗░░██╗██████╗░███████╗██████╗░██╗░░░░░░█████╗░███╗░░██╗██████╗░░
░██║░░██╗░░██║██╔══██╗████╗░██║██╔══██╗██╔════╝██╔══██╗██║░░░░░██╔══██╗████╗░██║██╔══██╗░
░╚██╗████╗██╔╝██║░░██║██╔██╗██║██║░░██║█████╗░░██████╔╝██║░░░░░███████║██╔██╗██║██║░░██║░
░░████╔═████║░██║░░██║██║╚████║██║░░██║██╔══╝░░██╔══██╗██║░░░░░██╔══██║██║╚████║██║░░██║░
░░╚██╔╝░╚██╔╝░╚█████╔╝██║░╚███║██████╔╝███████╗██║░░██║███████╗██║░░██║██║░╚███║██████╔╝░
░░░╚═╝░░░╚═╝░░░╚════╝░╚═╝░░╚══╝╚═════╝░╚══════╝╚═╝░░╚═╝╚══════╝╚═╝░░╚═╝╚═╝░░╚══╝╚═════╝░░
https://defi.sucks
*/
import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol';
import {IODSafeManager} from '@interfaces/proxies/IODSafeManager.sol';
import {Authorizable, IAuthorizable} from '@contracts/utils/Authorizable.sol';
import {Modifiable} from '@contracts/utils/Modifiable.sol';
import {IModifiablePerCollateral, ModifiablePerCollateral} from '@contracts/utils/ModifiablePerCollateral.sol';
import {Disableable} from '@contracts/utils/Disableable.sol';
import {Encoding} from '@libraries/Encoding.sol';
import {Math, RAY} from '@libraries/Math.sol';
import {Assertions} from '@libraries/Assertions.sol';
import {EnumerableSet} from '@openzeppelin/utils/structs/EnumerableSet.sol';
/**
* @title SAFEEngine
* @notice Core contract that manages the state of the SAFE system
*/
contract SAFEEngine is Authorizable, Disableable, Modifiable, ModifiablePerCollateral, ISAFEEngine {
using Math for uint256;
using Encoding for bytes;
using EnumerableSet for EnumerableSet.Bytes32Set;
using Assertions for address;
// --- Data ---
/// @inheritdoc ISAFEEngine
// solhint-disable-next-line private-vars-leading-underscore
SAFEEngineParams public _params;
/// @notice safe manager contract for verifying safe validity
// solhint-disable-next-line private-vars-leading-underscore
IODSafeManager public _odSafeManager;
/// @inheritdoc ISAFEEngine
// solhint-disable-next-line private-vars-leading-underscore
mapping(bytes32 _cType => SAFEEngineCollateralParams) public _cParams;
/// @inheritdoc ISAFEEngine
// solhint-disable-next-line private-vars-leading-underscore
mapping(bytes32 _cType => SAFEEngineCollateralData) public _cData;
/// @inheritdoc ISAFEEngine
// solhint-disable-next-line private-vars-leading-underscore
mapping(bytes32 _cType => mapping(address _safe => SAFE)) public _safes;
/// @inheritdoc ISAFEEngine
mapping(address _caller => mapping(address _account => uint256 _isAllowed)) public safeRights;
/// @inheritdoc ISAFEEngine
function params() external view returns (SAFEEngineParams memory _safeEngineParams) {
return _params;
}
/// @inheritdoc ISAFEEngine
function cParams(bytes32 _cType) external view returns (SAFEEngineCollateralParams memory _safeEngineCParams) {
return _cParams[_cType];
}
/// @inheritdoc ISAFEEngine
function cData(bytes32 _cType) external view returns (SAFEEngineCollateralData memory _safeEngineCData) {
return _cData[_cType];
}
/// @inheritdoc ISAFEEngine
function safes(bytes32 _cType, address _safe) external view returns (SAFE memory _safeData) {
return _safes[_cType][_safe];
}
/// @inheritdoc ISAFEEngine
function odSafeManager() external view returns (address _safeManager) {
return address(_odSafeManager);
}
// --- Balances ---
/// @inheritdoc ISAFEEngine
mapping(bytes32 _cType => mapping(address _safe => uint256 _wad)) public tokenCollateral;
/// @inheritdoc ISAFEEngine
mapping(address _safe => uint256 _rad) public coinBalance;
/// @inheritdoc ISAFEEngine
mapping(address _safe => uint256 _rad) public debtBalance;
/// @inheritdoc ISAFEEngine
uint256 public /* RAD */ globalDebt;
/// @inheritdoc ISAFEEngine
uint256 public /* RAD */ globalUnbackedDebt;
// --- Init ---
/**
* @param _safeEngineParams Initial SAFEEngine valid parameters struct
*/
constructor(SAFEEngineParams memory _safeEngineParams) Authorizable(msg.sender) validParams {
_params = _safeEngineParams;
}
function initializeSafeManager() external {
if (address(_odSafeManager) == address(0)) _odSafeManager = IODSafeManager(msg.sender);
}
// --- Fungibility ---
/// @inheritdoc ISAFEEngine
function transferCollateral(
bytes32 _cType,
address _source,
address _destination,
uint256 _wad
) external isSAFEAllowed(_source, msg.sender) {
tokenCollateral[_cType][_source] -= _wad;
tokenCollateral[_cType][_destination] += _wad;
emit TransferCollateral(_cType, _source, _destination, _wad);
}
/// @inheritdoc ISAFEEngine
function transferInternalCoins(
address _source,
address _destination,
uint256 _rad
) external isSAFEAllowed(_source, msg.sender) {
coinBalance[_source] -= _rad;
coinBalance[_destination] += _rad;
emit TransferInternalCoins(_source, _destination, _rad);
}
/// @inheritdoc ISAFEEngine
function modifyCollateralBalance(bytes32 _cType, address _account, int256 _wad) external isAuthorized {
_modifyCollateralBalance(_cType, _account, _wad);
}
// --- SAFE Manipulation ---
/// @inheritdoc ISAFEEngine
function modifySAFECollateralization(
bytes32 _cType,
address _safe,
address _collateralSource,
address _debtDestination,
int256 _deltaCollateral,
int256 _deltaDebt
) external whenEnabled {
if (_odSafeManager.safeHandlerToSafeId(_safe) == 0) revert SAFEEng_NotSAFEAllowed();
SAFEEngineCollateralData storage __cData = _cData[_cType];
// collateral type has been initialised
if (__cData.accumulatedRate == 0) revert SAFEEng_CollateralTypeNotInitialized();
_modifyCollateralBalance(_cType, _collateralSource, -_deltaCollateral);
_emitTransferCollateral(_cType, address(0), _safe, _deltaCollateral);
_modifySAFECollateralization(_cType, _safe, _deltaCollateral, _deltaDebt);
__cData.debtAmount = __cData.debtAmount.add(_deltaDebt);
__cData.lockedAmount = __cData.lockedAmount.add(_deltaCollateral);
int256 _deltaAdjustedDebt = __cData.accumulatedRate.mul(_deltaDebt);
_modifyInternalCoins(_debtDestination, _deltaAdjustedDebt);
// --- Safety checks ---
{
SAFEEngineCollateralParams memory __cParams = _cParams[_cType];
SAFE memory _safeData = _safes[_cType][_safe];
uint256 _totalDebtIssued = __cData.accumulatedRate * _safeData.generatedDebt;
// either debt is increased (generated) and debt ceilings are not exceeded, or debt destination consents
if (_deltaDebt > 0) {
if (globalDebt > _params.globalDebtCeiling) revert SAFEEng_GlobalDebtCeilingHit();
if (__cData.debtAmount * __cData.accumulatedRate > __cParams.debtCeiling) {
revert SAFEEng_CollateralDebtCeilingHit();
}
if (_safeData.generatedDebt > _params.safeDebtCeiling) revert SAFEEng_SAFEDebtCeilingHit();
} else {
if (!canModifySAFE(_debtDestination, msg.sender)) revert SAFEEng_NotDebtDstAllowed();
}
// either safe is less risky, or it is still safe and the owner consents
if (_deltaDebt > 0 || _deltaCollateral < 0) {
if (_totalDebtIssued > _safeData.lockedCollateral * __cData.safetyPrice) revert SAFEEng_SAFENotSafe();
if (!canModifySAFE(_safe, msg.sender)) revert SAFEEng_NotSAFEAllowed();
}
// either collateral is decreased (returned), or collateral source consents
if (_deltaCollateral > 0) {
if (!canModifySAFE(_collateralSource, msg.sender)) revert SAFEEng_NotCollateralSrcAllowed();
}
// either safe has no debt, or a non-dusty amount
if (_safeData.generatedDebt != 0 && _totalDebtIssued < __cParams.debtFloor) revert SAFEEng_DustySAFE();
}
emit ModifySAFECollateralization(_cType, _safe, _collateralSource, _debtDestination, _deltaCollateral, _deltaDebt);
}
// --- SAFE Fungibility ---
/// @inheritdoc ISAFEEngine
function transferSAFECollateralAndDebt(
bytes32 _cType,
address _src,
address _dst,
int256 _deltaCollateral,
int256 _deltaDebt
) external isSAFEAllowed(_src, msg.sender) isSAFEAllowed(_dst, msg.sender) {
_modifySAFECollateralization(_cType, _src, -_deltaCollateral, -_deltaDebt);
_emitTransferCollateral(_cType, _src, _dst, _deltaCollateral);
_modifySAFECollateralization(_cType, _dst, _deltaCollateral, _deltaDebt);
// --- Safety checks ---
{
SAFE memory _srcSAFE = _safes[_cType][_src];
SAFE memory _dstSAFE = _safes[_cType][_dst];
SAFEEngineCollateralParams memory __cParams = _cParams[_cType];
SAFEEngineCollateralData memory __cData = _cData[_cType];
uint256 _srcTotalDebtIssued = _srcSAFE.generatedDebt * __cData.accumulatedRate;
uint256 _dstTotalDebtIssued = _dstSAFE.generatedDebt * __cData.accumulatedRate;
// both sides below debt ceiling
if (_srcSAFE.generatedDebt > _params.safeDebtCeiling || _dstSAFE.generatedDebt > _params.safeDebtCeiling) {
revert SAFEEng_SAFEDebtCeilingHit();
}
// both sides safe
if (
_srcTotalDebtIssued > _srcSAFE.lockedCollateral * __cData.safetyPrice
|| _dstTotalDebtIssued > _dstSAFE.lockedCollateral * __cData.safetyPrice
) revert SAFEEng_SAFENotSafe();
// both sides non-dusty
if (
(_srcTotalDebtIssued < __cParams.debtFloor && _srcSAFE.generatedDebt != 0)
|| (_dstTotalDebtIssued < __cParams.debtFloor && _dstSAFE.generatedDebt != 0)
) revert SAFEEng_DustySAFE();
}
emit TransferSAFECollateralAndDebt(_cType, _src, _dst, _deltaCollateral, _deltaDebt);
}
// --- SAFE Confiscation ---
/// @inheritdoc ISAFEEngine
function confiscateSAFECollateralAndDebt(
bytes32 _cType,
address _safe,
address _collateralSource,
address _debtDestination,
int256 _deltaCollateral,
int256 _deltaDebt
) external isAuthorized {
SAFEEngineCollateralData storage __cData = _cData[_cType];
_modifyCollateralBalance(_cType, _collateralSource, -_deltaCollateral);
_emitTransferCollateral(_cType, address(0), _safe, _deltaCollateral);
_modifySAFECollateralization(_cType, _safe, _deltaCollateral, _deltaDebt);
__cData.debtAmount = __cData.debtAmount.add(_deltaDebt);
__cData.lockedAmount = __cData.lockedAmount.add(_deltaCollateral);
int256 _deltaTotalIssuedDebt = __cData.accumulatedRate.mul(_deltaDebt);
debtBalance[_debtDestination] = debtBalance[_debtDestination].sub(_deltaTotalIssuedDebt);
globalUnbackedDebt = globalUnbackedDebt.sub(_deltaTotalIssuedDebt);
emit ConfiscateSAFECollateralAndDebt(
_cType, _safe, _collateralSource, _debtDestination, _deltaCollateral, _deltaDebt
);
}
// --- Settlement ---
/// @inheritdoc ISAFEEngine
function settleDebt(uint256 _rad) external {
address _account = msg.sender;
debtBalance[_account] -= _rad;
_modifyInternalCoins(_account, -_rad.toInt());
globalUnbackedDebt -= _rad;
emit SettleDebt(_account, _rad);
}
/// @inheritdoc ISAFEEngine
function createUnbackedDebt(address _debtDestination, address _coinDestination, uint256 _rad) external isAuthorized {
debtBalance[_debtDestination] += _rad;
_modifyInternalCoins(_coinDestination, _rad.toInt());
globalUnbackedDebt += _rad;
emit CreateUnbackedDebt(_debtDestination, _coinDestination, _rad);
}
// --- Update ---
/// @inheritdoc ISAFEEngine
function updateAccumulatedRate(
bytes32 _cType,
address _surplusDst,
int256 _rateMultiplier
) external isAuthorized whenEnabled {
SAFEEngineCollateralData storage __cData = _cData[_cType];
__cData.accumulatedRate = __cData.accumulatedRate.add(_rateMultiplier);
int256 _deltaSurplus = __cData.debtAmount.mul(_rateMultiplier);
_modifyInternalCoins(_surplusDst, _deltaSurplus);
emit UpdateAccumulatedRate(_cType, _surplusDst, _rateMultiplier);
}
/// @inheritdoc ISAFEEngine
function updateCollateralPrice(
bytes32 _cType,
uint256 _safetyPrice,
uint256 _liquidationPrice
) external isAuthorized whenEnabled {
_cData[_cType].safetyPrice = _safetyPrice;
_cData[_cType].liquidationPrice = _liquidationPrice;
emit UpdateCollateralPrice(_cType, _safetyPrice, _liquidationPrice);
}
// --- Authorization ---
/**
* @notice Add auth to an account
* @param _account Account to add auth to
*/
/**
* @dev This overriden method avoids adding new authorizations after the contract has been disabled
* @inheritdoc IAuthorizable
*/
function addAuthorization(address _account) external override(Authorizable, IAuthorizable) isAuthorized whenEnabled {
_addAuthorization(_account);
}
/**
* @notice Remove auth from an account
* @param _account Account to remove auth from
*/
function removeAuthorization(address _account)
external
override(Authorizable, IAuthorizable)
isAuthorized
whenEnabled
{
_removeAuthorization(_account);
}
/// @inheritdoc ISAFEEngine
function approveSAFEModification(address _account) external {
safeRights[msg.sender][_account] = 1;
emit ApproveSAFEModification(msg.sender, _account);
}
/// @inheritdoc ISAFEEngine
function denySAFEModification(address _account) external {
safeRights[msg.sender][_account] = 0;
emit DenySAFEModification(msg.sender, _account);
}
/// @inheritdoc ISAFEEngine
function canModifySAFE(address _safe, address _account) public view returns (bool _canModifySafe) {
return _safe == _account || safeRights[_safe][_account] == 1;
}
// --- Internals ---
function _modifyCollateralBalance(bytes32 _cType, address _account, int256 _wad) internal {
tokenCollateral[_cType][_account] = tokenCollateral[_cType][_account].add(_wad);
_emitTransferCollateral(_cType, address(0), _account, _wad);
}
function _modifyInternalCoins(address _dst, int256 _rad) internal {
coinBalance[_dst] = coinBalance[_dst].add(_rad);
globalDebt = globalDebt.add(_rad);
if (_rad > 0) emit TransferInternalCoins(address(0), _dst, uint256(_rad));
else emit TransferInternalCoins(_dst, address(0), uint256(-_rad));
}
function _modifySAFECollateralization(
bytes32 _cType,
address _safe,
int256 _deltaCollateral,
int256 _deltaDebt
) internal {
SAFE storage _safeData = _safes[_cType][_safe];
_safeData.lockedCollateral = _safeData.lockedCollateral.add(_deltaCollateral);
_safeData.generatedDebt = _safeData.generatedDebt.add(_deltaDebt);
_emitTransferCollateral(_cType, _safe, address(this), _deltaCollateral);
}
function _emitTransferCollateral(bytes32 _cType, address _src, address _dst, int256 _deltaCollateral) internal {
if (_deltaCollateral == 0) return;
if (_deltaCollateral >= 0) {
emit TransferCollateral(_cType, _src, _dst, uint256(_deltaCollateral));
} else {
emit TransferCollateral(_cType, _dst, _src, uint256(-_deltaCollateral));
}
}
// --- Administration ---
/// @inheritdoc ModifiablePerCollateral
function _initializeCollateralType(bytes32 _cType, bytes memory _collateralParams) internal override whenEnabled {
(SAFEEngineCollateralParams memory _safeEngineCParams) = abi.decode(_collateralParams, (SAFEEngineCollateralParams));
_cData[_cType].accumulatedRate = RAY;
_cParams[_cType] = _safeEngineCParams;
}
/// @inheritdoc Modifiable
function _modifyParameters(bytes32 _param, bytes memory _data) internal override {
uint256 _uint256 = _data.toUint256();
if (_param == 'globalDebtCeiling') _params.globalDebtCeiling = _uint256;
else if (_param == 'safeDebtCeiling') _params.safeDebtCeiling = _uint256;
else if (_param == 'odSafeManager') _odSafeManager = IODSafeManager(_data.toAddress().assertNonNull());
else revert UnrecognizedParam();
}
/// @inheritdoc ModifiablePerCollateral
function _modifyParameters(bytes32 _cType, bytes32 _param, bytes memory _data) internal override {
uint256 _uint256 = _data.toUint256();
if (!_collateralList.contains(_cType)) revert UnrecognizedCType();
if (_param == 'debtCeiling') _cParams[_cType].debtCeiling = _uint256;
else if (_param == 'debtFloor') _cParams[_cType].debtFloor = _uint256;
else revert UnrecognizedParam();
}
// --- Modifiers ---
modifier isSAFEAllowed(address _safe, address _account) {
if (!canModifySAFE(_safe, _account)) revert SAFEEng_NotSAFEAllowed();
_;
}
}