forked from Synthetixio/synthetix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSynthetixState.sol
298 lines (252 loc) · 9.83 KB
/
SynthetixState.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
/*
-----------------------------------------------------------------
FILE INFORMATION
-----------------------------------------------------------------
file: SynthetixState.sol
version: 1.0
author: Kevin Brown
date: 2018-10-19
-----------------------------------------------------------------
MODULE DESCRIPTION
-----------------------------------------------------------------
A contract that holds issuance state and preferred currency of
users in the Synthetix system.
This contract is used side by side with the Synthetix contract
to make it easier to upgrade the contract logic while maintaining
issuance state.
The Synthetix contract is also quite large and on the edge of
being beyond the contract size limit without moving this information
out to another contract.
The first deployed contract would create this state contract,
using it as its store of issuance data.
When a new contract is deployed, it links to the existing
state contract, whose owner would then change its associated
contract to the new one.
-----------------------------------------------------------------
*/
pragma solidity 0.4.25;
import "./Synthetix.sol";
import "./LimitedSetup.sol";
import "./SafeDecimalMath.sol";
import "./State.sol";
/**
* @title Synthetix State
* @notice Stores issuance information and preferred currency information of the Synthetix contract.
*/
contract SynthetixState is State, LimitedSetup {
using SafeMath for uint;
using SafeDecimalMath for uint;
// A struct for handing values associated with an individual user's debt position
struct IssuanceData {
// Percentage of the total debt owned at the time
// of issuance. This number is modified by the global debt
// delta array. You can figure out a user's exit price and
// collateralisation ratio using a combination of their initial
// debt and the slice of global debt delta which applies to them.
uint initialDebtOwnership;
// This lets us know when (in relative terms) the user entered
// the debt pool so we can calculate their exit price and
// collateralistion ratio
uint debtEntryIndex;
}
// Issued synth balances for individual fee entitlements and exit price calculations
mapping(address => IssuanceData) public issuanceData;
// The total count of people that have outstanding issued synths in any flavour
uint public totalIssuerCount;
// Global debt pool tracking
uint[] public debtLedger;
// Import state
uint public importedXDRAmount;
// A quantity of synths greater than this ratio
// may not be issued against a given value of SNX.
uint public issuanceRatio = SafeDecimalMath.unit() / 5;
// No more synths may be issued than the value of SNX backing them.
uint constant MAX_ISSUANCE_RATIO = SafeDecimalMath.unit();
// Users can specify their preferred currency, in which case all synths they receive
// will automatically exchange to that preferred currency upon receipt in their wallet
mapping(address => bytes4) public preferredCurrency;
/**
* @dev Constructor
* @param _owner The address which controls this contract.
* @param _associatedContract The ERC20 contract whose state this composes.
*/
constructor(address _owner, address _associatedContract)
State(_owner, _associatedContract)
LimitedSetup(1 weeks)
public
{}
/* ========== SETTERS ========== */
/**
* @notice Set issuance data for an address
* @dev Only the associated contract may call this.
* @param account The address to set the data for.
* @param initialDebtOwnership The initial debt ownership for this address.
*/
function setCurrentIssuanceData(address account, uint initialDebtOwnership)
external
onlyAssociatedContract
{
issuanceData[account].initialDebtOwnership = initialDebtOwnership;
issuanceData[account].debtEntryIndex = debtLedger.length;
}
/**
* @notice Clear issuance data for an address
* @dev Only the associated contract may call this.
* @param account The address to clear the data for.
*/
function clearIssuanceData(address account)
external
onlyAssociatedContract
{
delete issuanceData[account];
}
/**
* @notice Increment the total issuer count
* @dev Only the associated contract may call this.
*/
function incrementTotalIssuerCount()
external
onlyAssociatedContract
{
totalIssuerCount = totalIssuerCount.add(1);
}
/**
* @notice Decrement the total issuer count
* @dev Only the associated contract may call this.
*/
function decrementTotalIssuerCount()
external
onlyAssociatedContract
{
totalIssuerCount = totalIssuerCount.sub(1);
}
/**
* @notice Append a value to the debt ledger
* @dev Only the associated contract may call this.
* @param value The new value to be added to the debt ledger.
*/
function appendDebtLedgerValue(uint value)
external
onlyAssociatedContract
{
debtLedger.push(value);
}
/**
* @notice Set preferred currency for a user
* @dev Only the associated contract may call this.
* @param account The account to set the preferred currency for
* @param currencyKey The new preferred currency
*/
function setPreferredCurrency(address account, bytes4 currencyKey)
external
onlyAssociatedContract
{
preferredCurrency[account] = currencyKey;
}
/**
* @notice Set the issuanceRatio for issuance calculations.
* @dev Only callable by the contract owner.
*/
function setIssuanceRatio(uint _issuanceRatio)
external
onlyOwner
{
require(_issuanceRatio <= MAX_ISSUANCE_RATIO, "New issuance ratio cannot exceed MAX_ISSUANCE_RATIO");
issuanceRatio = _issuanceRatio;
emit IssuanceRatioUpdated(_issuanceRatio);
}
/**
* @notice Import issuer data from the old Synthetix contract before multicurrency
* @dev Only callable by the contract owner, and only for 1 week after deployment.
*/
function importIssuerData(address[] accounts, uint[] sUSDAmounts)
external
onlyOwner
onlyDuringSetup
{
require(accounts.length == sUSDAmounts.length, "Length mismatch");
for (uint8 i = 0; i < accounts.length; i++) {
_addToDebtRegister(accounts[i], sUSDAmounts[i]);
}
}
/**
* @notice Import issuer data from the old Synthetix contract before multicurrency
* @dev Only used from importIssuerData above, meant to be disposable
*/
function _addToDebtRegister(address account, uint amount)
internal
{
// This code is duplicated from Synthetix so that we can call it directly here
// during setup only.
Synthetix synthetix = Synthetix(associatedContract);
// What is the value of the requested debt in XDRs?
uint xdrValue = synthetix.effectiveValue("sUSD", amount, "XDR");
// What is the value that we've previously imported?
uint totalDebtIssued = importedXDRAmount;
// What will the new total be including the new value?
uint newTotalDebtIssued = xdrValue.add(totalDebtIssued);
// Save that for the next import.
importedXDRAmount = newTotalDebtIssued;
// What is their percentage (as a high precision int) of the total debt?
uint debtPercentage = xdrValue.divideDecimalRoundPrecise(newTotalDebtIssued);
// And what effect does this percentage have on the global debt holding of other issuers?
// The delta specifically needs to not take into account any existing debt as it's already
// accounted for in the delta from when they issued previously.
// The delta is a high precision integer.
uint delta = SafeDecimalMath.preciseUnit().sub(debtPercentage);
uint existingDebt = synthetix.debtBalanceOf(account, "XDR");
// And what does their debt ownership look like including this previous stake?
if (existingDebt > 0) {
debtPercentage = xdrValue.add(existingDebt).divideDecimalRoundPrecise(newTotalDebtIssued);
}
// Are they a new issuer? If so, record them.
if (issuanceData[account].initialDebtOwnership == 0) {
totalIssuerCount = totalIssuerCount.add(1);
}
// Save the debt entry parameters
issuanceData[account].initialDebtOwnership = debtPercentage;
issuanceData[account].debtEntryIndex = debtLedger.length;
// And if we're the first, push 1 as there was no effect to any other holders, otherwise push
// the change for the rest of the debt holders. The debt ledger holds high precision integers.
if (debtLedger.length > 0) {
debtLedger.push(
debtLedger[debtLedger.length - 1].multiplyDecimalRoundPrecise(delta)
);
} else {
debtLedger.push(SafeDecimalMath.preciseUnit());
}
}
/* ========== VIEWS ========== */
/**
* @notice Retrieve the length of the debt ledger array
*/
function debtLedgerLength()
external
view
returns (uint)
{
return debtLedger.length;
}
/**
* @notice Retrieve the most recent entry from the debt ledger
*/
function lastDebtLedgerEntry()
external
view
returns (uint)
{
return debtLedger[debtLedger.length - 1];
}
/**
* @notice Query whether an account has issued and has an outstanding debt balance
* @param account The address to query for
*/
function hasIssued(address account)
external
view
returns (bool)
{
return issuanceData[account].initialDebtOwnership > 0;
}
event IssuanceRatioUpdated(uint newRatio);
}