UNPKG

13.9 kBJavaScriptView Raw
1'use strict';
2
3const w3utils = require('web3-utils');
4const abiDecoder = require('abi-decoder');
5
6// load the data in explicitly (not programmatically) so webpack knows what to bundle
7const data = {
8 kovan: {
9 deployment: require('./publish/deployed/kovan/deployment.json'),
10 versions: require('./publish/deployed/kovan/versions.json'),
11 synths: require('./publish/deployed/kovan/synths.json'),
12 rewards: require('./publish/deployed/kovan/rewards.json'),
13 feeds: require('./publish/deployed/kovan/feeds.json'),
14 },
15 rinkeby: {
16 deployment: require('./publish/deployed/rinkeby/deployment.json'),
17 versions: require('./publish/deployed/rinkeby/versions.json'),
18 synths: require('./publish/deployed/rinkeby/synths.json'),
19 rewards: require('./publish/deployed/rinkeby/rewards.json'),
20 feeds: require('./publish/deployed/rinkeby/feeds.json'),
21 },
22 ropsten: {
23 deployment: require('./publish/deployed/ropsten/deployment.json'),
24 versions: require('./publish/deployed/ropsten/versions.json'),
25 synths: require('./publish/deployed/ropsten/synths.json'),
26 rewards: require('./publish/deployed/ropsten/rewards.json'),
27 feeds: require('./publish/deployed/ropsten/feeds.json'),
28 },
29 mainnet: {
30 deployment: require('./publish/deployed/mainnet/deployment.json'),
31 versions: require('./publish/deployed/mainnet/versions.json'),
32 synths: require('./publish/deployed/mainnet/synths.json'),
33 rewards: require('./publish/deployed/mainnet/rewards.json'),
34 feeds: require('./publish/deployed/mainnet/feeds.json'),
35 },
36};
37
38const assets = require('./publish/assets.json');
39
40const networks = ['local', 'kovan', 'rinkeby', 'ropsten', 'mainnet'];
41
42const networkToChainId = {
43 mainnet: 1,
44 ropsten: 3,
45 rinkeby: 4,
46 kovan: 42,
47};
48
49const constants = {
50 BUILD_FOLDER: 'build',
51 CONTRACTS_FOLDER: 'contracts',
52 COMPILED_FOLDER: 'compiled',
53 FLATTENED_FOLDER: 'flattened',
54 AST_FOLDER: 'ast',
55
56 CONFIG_FILENAME: 'config.json',
57 SYNTHS_FILENAME: 'synths.json',
58 STAKING_REWARDS_FILENAME: 'rewards.json',
59 OWNER_ACTIONS_FILENAME: 'owner-actions.json',
60 DEPLOYMENT_FILENAME: 'deployment.json',
61 VERSIONS_FILENAME: 'versions.json',
62 FEEDS_FILENAME: 'feeds.json',
63
64 AST_FILENAME: 'asts.json',
65
66 ZERO_ADDRESS: '0x' + '0'.repeat(40),
67
68 inflationStartTimestampInSecs: 1551830400, // 2019-03-06T00:00:00Z
69};
70
71// The solidity defaults are managed here in the same format they will be stored, hence all
72// numbers are converted to strings and those with 18 decimals are also converted to wei amounts
73const defaults = {
74 WAITING_PERIOD_SECS: (60 * 5).toString(), // 5 mins
75 PRICE_DEVIATION_THRESHOLD_FACTOR: w3utils.toWei('3'),
76 TRADING_REWARDS_ENABLED: false,
77 ISSUANCE_RATIO: w3utils
78 .toBN(1)
79 .mul(w3utils.toBN(1e18))
80 .div(w3utils.toBN(6))
81 .toString(), // 1/6 = 0.16666666667
82 FEE_PERIOD_DURATION: (3600 * 24 * 7).toString(), // 1 week
83 TARGET_THRESHOLD: '1', // 1% target threshold (it will be converted to a decimal when set)
84 LIQUIDATION_DELAY: (3600 * 24 * 3).toString(), // 3 days
85 LIQUIDATION_RATIO: w3utils.toWei('0.5'), // 200% cratio
86 LIQUIDATION_PENALTY: w3utils.toWei('0.1'), // 10% penalty
87 RATE_STALE_PERIOD: (3600 * 25).toString(), // 25 hours
88 EXCHANGE_FEE_RATES: {
89 forex: w3utils.toWei('0.003'),
90 commodity: w3utils.toWei('0.003'),
91 equities: w3utils.toWei('0.003'),
92 crypto: w3utils.toWei('0.003'),
93 index: w3utils.toWei('0.003'),
94 },
95 MINIMUM_STAKE_TIME: (3600 * 24).toString(), // 1 days
96 AGGREGATOR_WARNING_FLAGS: {
97 mainnet: '0x4A5b9B4aD08616D11F3A402FF7cBEAcB732a76C6',
98 kovan: '0x6292aa9a6650ae14fbf974e5029f36f95a1848fd',
99 },
100};
101
102/**
103 * Converts a string into a hex representation of bytes32, with right padding
104 */
105const toBytes32 = key => w3utils.rightPad(w3utils.asciiToHex(key), 64);
106
107const getPathToNetwork = ({ network = 'mainnet', file = '', path } = {}) =>
108 path.join(__dirname, 'publish', 'deployed', network, file);
109
110// Pass in fs and path to avoid webpack wrapping those
111const loadDeploymentFile = ({ network, path, fs, deploymentPath }) => {
112 if (!deploymentPath && network !== 'local' && (!path || !fs)) {
113 return data[network].deployment;
114 }
115 const pathToDeployment = deploymentPath
116 ? path.join(deploymentPath, constants.DEPLOYMENT_FILENAME)
117 : getPathToNetwork({ network, path, file: constants.DEPLOYMENT_FILENAME });
118 if (!fs.existsSync(pathToDeployment)) {
119 throw Error(`Cannot find deployment for network: ${network}.`);
120 }
121 return JSON.parse(fs.readFileSync(pathToDeployment));
122};
123
124/**
125 * Retrieve the list of targets for the network - returning the name, address, source file and link to etherscan
126 */
127const getTarget = ({ network = 'mainnet', contract, path, fs, deploymentPath } = {}) => {
128 const deployment = loadDeploymentFile({ network, path, fs, deploymentPath });
129 if (contract) return deployment.targets[contract];
130 else return deployment.targets;
131};
132
133/**
134 * Retrieve the list of solidity sources for the network - returning the abi and bytecode
135 */
136const getSource = ({ network = 'mainnet', contract, path, fs, deploymentPath } = {}) => {
137 const deployment = loadDeploymentFile({ network, path, fs, deploymentPath });
138 if (contract) return deployment.sources[contract];
139 else return deployment.sources;
140};
141
142/**
143 * Retrieve the ASTs for the source contracts
144 */
145const getAST = ({ source, path, fs, match = /^contracts\// } = {}) => {
146 let fullAST;
147 if (path && fs) {
148 const pathToAST = path.resolve(
149 __dirname,
150 constants.BUILD_FOLDER,
151 constants.AST_FOLDER,
152 constants.AST_FILENAME
153 );
154 if (!fs.existsSync(pathToAST)) {
155 throw Error('Cannot find AST');
156 }
157 fullAST = JSON.parse(fs.readFileSync(pathToAST));
158 } else {
159 // Note: The below cannot be required as the build folder is not stored
160 // in code (only in the published module).
161 // The solution involves tracking these after each commit in another file
162 // somewhere persisted in the codebase - JJM
163 // data.ast = require('./build/ast/asts.json'),
164 if (!data.ast) {
165 throw Error('AST currently not supported in browser mode');
166 }
167 fullAST = data.ast;
168 }
169
170 // remove anything not matching the pattern
171 const ast = Object.entries(fullAST)
172 .filter(([astEntryKey]) => match.test(astEntryKey))
173 .reduce((memo, [key, val]) => {
174 memo[key] = val;
175 return memo;
176 }, {});
177
178 if (source && source in ast) {
179 return ast[source];
180 } else if (source) {
181 // try to find the source without a path
182 const [key, entry] =
183 Object.entries(ast).find(([astEntryKey]) => astEntryKey.includes('/' + source)) || [];
184 if (!key || !entry) {
185 throw Error(`Cannot find AST entry for source: ${source}`);
186 }
187 return { [key]: entry };
188 } else {
189 return ast;
190 }
191};
192
193const getFeeds = ({ network, path, fs, deploymentPath } = {}) => {
194 let feeds;
195
196 if (!deploymentPath && network !== 'local' && (!path || !fs)) {
197 feeds = data[network].feeds;
198 } else {
199 const pathToFeeds = deploymentPath
200 ? path.join(deploymentPath, constants.FEEDS_FILENAME)
201 : getPathToNetwork({
202 network,
203 path,
204 file: constants.FEEDS_FILENAME,
205 });
206 if (!fs.existsSync(pathToFeeds)) {
207 throw Error(`Cannot find feeds file.`);
208 }
209 feeds = JSON.parse(fs.readFileSync(pathToFeeds));
210 }
211
212 // now mix in the asset data
213 return Object.entries(feeds).reduce((memo, [asset, entry]) => {
214 memo[asset] = Object.assign({}, assets[asset], entry);
215 return memo;
216 }, {});
217};
218/**
219 * Retrieve ths list of synths for the network - returning their names, assets underlying, category, sign, description, and
220 * optional index and inverse properties
221 */
222const getSynths = ({ network = 'mainnet', path, fs, deploymentPath } = {}) => {
223 let synths;
224
225 if (!deploymentPath && network !== 'local' && (!path || !fs)) {
226 synths = data[network].synths;
227 } else {
228 const pathToSynthList = deploymentPath
229 ? path.join(deploymentPath, constants.SYNTHS_FILENAME)
230 : getPathToNetwork({ network, path, file: constants.SYNTHS_FILENAME });
231 if (!fs.existsSync(pathToSynthList)) {
232 throw Error(`Cannot find synth list.`);
233 }
234 synths = JSON.parse(fs.readFileSync(pathToSynthList));
235 }
236
237 const feeds = getFeeds({ network, path, fs, deploymentPath });
238
239 // copy all necessary index parameters from the longs to the corresponding shorts
240 return synths.map(synth => {
241 // mixin the asset details
242 synth = Object.assign({}, assets[synth.asset], synth);
243
244 if (feeds[synth.asset]) {
245 // mixing the feed
246 synth = Object.assign({}, feeds[synth.asset], synth);
247 }
248
249 if (synth.inverted) {
250 synth.desc = `Inverse ${synth.desc}`;
251 }
252 // replace an index placeholder with the index details
253 if (typeof synth.index === 'string') {
254 const { index } = synths.find(({ name }) => name === synth.index) || {};
255 if (!index) {
256 throw Error(
257 `While processing ${synth.name}, it's index mapping "${synth.index}" cannot be found - this is an error in the deployment config and should be fixed`
258 );
259 }
260 synth = Object.assign({}, synth, { index });
261 }
262
263 if (synth.index) {
264 synth.index = synth.index.map(indexEntry => {
265 return Object.assign({}, assets[indexEntry.asset], indexEntry);
266 });
267 }
268
269 return synth;
270 });
271};
272
273/**
274 * Retrieve the list of staking rewards for the network - returning this names, stakingToken, and rewardToken
275 */
276const getStakingRewards = ({ network = 'mainnet', path, fs, deploymentPath } = {}) => {
277 if (!deploymentPath && network !== 'local' && (!path || !fs)) {
278 return data[network].rewards;
279 }
280
281 const pathToStakingRewardsList = deploymentPath
282 ? path.join(deploymentPath, constants.STAKING_REWARDS_FILENAME)
283 : getPathToNetwork({
284 network,
285 path,
286 file: constants.STAKING_REWARDS_FILENAME,
287 });
288 if (!fs.existsSync(pathToStakingRewardsList)) {
289 return [];
290 }
291 return JSON.parse(fs.readFileSync(pathToStakingRewardsList));
292};
293
294/**
295 * Retrieve the list of system user addresses
296 */
297const getUsers = ({ network = 'mainnet', user } = {}) => {
298 const testnetOwner = '0xB64fF7a4a33Acdf48d97dab0D764afD0F6176882';
299 const base = {
300 owner: testnetOwner,
301 deployer: testnetOwner,
302 marketClosure: testnetOwner,
303 oracle: '0xac1e8B385230970319906C03A1d8567e3996d1d5',
304 fee: '0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF',
305 zero: '0x' + '0'.repeat(40),
306 };
307
308 const map = {
309 mainnet: Object.assign({}, base, {
310 owner: '0xEb3107117FEAd7de89Cd14D463D340A2E6917769',
311 deployer: '0xDe910777C787903F78C89e7a0bf7F4C435cBB1Fe',
312 marketClosure: '0xC105Ea57Eb434Fbe44690d7Dec2702e4a2FBFCf7',
313 oracle: '0xaC1ED4Fabbd5204E02950D68b6FC8c446AC95362',
314 }),
315 kovan: Object.assign({}, base),
316 rinkeby: Object.assign({}, base),
317 ropsten: Object.assign({}, base),
318 };
319
320 const users = Object.entries(map[network]).map(([key, value]) => ({ name: key, address: value }));
321
322 return user ? users.find(({ name }) => name === user) : users;
323};
324
325const getVersions = ({
326 network = 'mainnet',
327 path,
328 fs,
329 deploymentPath,
330 byContract = false,
331} = {}) => {
332 let versions;
333
334 if (!deploymentPath && network !== 'local' && (!path || !fs)) {
335 versions = data[network].versions;
336 } else {
337 const pathToVersions = deploymentPath
338 ? path.join(deploymentPath, constants.VERSIONS_FILENAME)
339 : getPathToNetwork({ network, path, file: constants.VERSIONS_FILENAME });
340 if (!fs.existsSync(pathToVersions)) {
341 throw Error(`Cannot find versions for network.`);
342 }
343 versions = JSON.parse(fs.readFileSync(pathToVersions));
344 }
345
346 if (byContract) {
347 // compile from the contract perspective
348 return Object.values(versions).reduce((memo, entry) => {
349 for (const [contract, contractEntry] of Object.entries(entry.contracts)) {
350 memo[contract] = memo[contract] || [];
351 memo[contract].push(contractEntry);
352 }
353 return memo;
354 }, {});
355 }
356 return versions;
357};
358
359const getSuspensionReasons = ({ code = undefined } = {}) => {
360 const suspensionReasonMap = {
361 1: 'System Upgrade',
362 2: 'Market Closure',
363 55: 'Circuit Breaker (Phase one)', // https://sips.synthetix.io/SIPS/sip-55
364 65: 'Decentralized Circuit Breaker (Phase two)', // https://sips.synthetix.io/SIPS/sip-65
365 99999: 'Emergency',
366 };
367
368 return code ? suspensionReasonMap[code] : suspensionReasonMap;
369};
370
371/**
372 * Retrieve the list of tokens used in the Synthetix protocol
373 */
374const getTokens = ({ network = 'mainnet', path, fs } = {}) => {
375 const synths = getSynths({ network, path, fs });
376 const targets = getTarget({ network, path, fs });
377
378 return [
379 {
380 symbol: 'SNX',
381 name: 'Synthetix',
382 address: targets.ProxyERC20.address,
383 decimals: 18,
384 },
385 ].concat(
386 synths
387 .filter(({ category }) => category !== 'internal')
388 .map(synth => ({
389 symbol: synth.name,
390 asset: synth.asset,
391 name: synth.desc,
392 address: targets[`Proxy${synth.name === 'sUSD' ? 'ERC20sUSD' : synth.name}`].address,
393 index: synth.index,
394 inverted: synth.inverted,
395 decimals: 18,
396 feed: synth.feed,
397 }))
398 .sort((a, b) => (a.symbol > b.symbol ? 1 : -1))
399 );
400};
401
402const decode = ({ network = 'mainnet', fs, path, data, target } = {}) => {
403 const sources = getSource({ network, path, fs });
404 for (const { abi } of Object.values(sources)) {
405 abiDecoder.addABI(abi);
406 }
407 const targets = getTarget({ network, path, fs });
408 let contract;
409 if (target) {
410 contract = Object.values(targets).filter(
411 ({ address }) => address.toLowerCase() === target.toLowerCase()
412 )[0].name;
413 }
414 return { method: abiDecoder.decodeMethod(data), contract };
415};
416
417const wrap = ({ network, fs, path }) =>
418 [
419 'decode',
420 'getAST',
421 'getPathToNetwork',
422 'getSource',
423 'getStakingRewards',
424 'getFeeds',
425 'getSynths',
426 'getTarget',
427 'getTokens',
428 'getUsers',
429 'getVersions',
430 ].reduce((memo, fnc) => {
431 memo[fnc] = (prop = {}) => module.exports[fnc](Object.assign({ network, fs, path }, prop));
432 return memo;
433 }, {});
434
435module.exports = {
436 constants,
437 decode,
438 defaults,
439 getAST,
440 getPathToNetwork,
441 getSource,
442 getStakingRewards,
443 getSuspensionReasons,
444 getFeeds,
445 getSynths,
446 getTarget,
447 getTokens,
448 getUsers,
449 getVersions,
450 networks,
451 networkToChainId,
452 toBytes32,
453 wrap,
454};