UNPKG

synthetix

Version:

The smart contracts which make up the Synthetix system. (synthetix.io)

1,012 lines (917 loc) 29.7 kB
'use strict'; const w3utils = require('web3-utils'); const abiDecoder = require('abi-decoder'); // load the data in explicitly (not programmatically) so webpack knows what to bundle const data = { mainnet: require('./publish/deployed/mainnet'), sepolia: require('./publish/deployed/sepolia'), 'sepolia-ovm': require('./publish/deployed/sepolia-ovm'), 'local-ovm': require('./publish/deployed/local-ovm'), 'mainnet-ovm': require('./publish/deployed/mainnet-ovm'), }; const assets = require('./publish/assets.json'); const nonUpgradeable = require('./publish/non-upgradeable.json'); const releases = require('./publish/releases.json'); const networks = ['local', 'mainnet', 'sepolia']; const chainIdMapping = Object.entries({ 1: { network: 'mainnet', }, // Hardhat fork of mainnet: https://hardhat.org/config/#hardhat-network 31337: { network: 'mainnet', fork: true, }, 10: { network: 'mainnet', useOvm: true, }, 11155111: { network: 'sepolia', }, 11155420: { network: 'sepolia', useOvm: true, }, '-1': { // no chain ID for this currently network: 'unknown', useOvm: true, }, // now append any defaults }).reduce((memo, [id, body]) => { memo[id] = Object.assign({ useOvm: false, fork: false }, body); return memo; }, {}); /** @type {(obj: {id: number} | number) => number} */ const getNetworkFromId = obj => { const id = typeof obj === 'number' ? obj : obj.id; return chainIdMapping[id]; }; const networkToChainId = Object.entries(chainIdMapping).reduce( (memo, [id, { network, useOvm, fork }]) => { memo[network + (useOvm ? '-ovm' : '') + (fork ? '-fork' : '')] = id; return memo; }, {} ); const constants = { BUILD_FOLDER: 'build', CONTRACTS_FOLDER: 'contracts', MIGRATIONS_FOLDER: 'migrations', COMPILED_FOLDER: 'compiled', FLATTENED_FOLDER: 'flattened', AST_FOLDER: 'ast', CONFIG_FILENAME: 'config.json', RELEASES_FILENAME: 'releases.json', PARAMS_FILENAME: 'params.json', SYNTHS_FILENAME: 'synths.json', STAKING_REWARDS_FILENAME: 'rewards.json', SHORTING_REWARDS_FILENAME: 'shorting-rewards.json', OWNER_ACTIONS_FILENAME: 'owner-actions.json', DEPLOYMENT_FILENAME: 'deployment.json', VERSIONS_FILENAME: 'versions.json', FEEDS_FILENAME: 'feeds.json', OFFCHAIN_FEEDS_FILENAME: 'offchain-feeds.json', FUTURES_MARKETS_FILENAME: 'futures-markets.json', PERPS_V2_MARKETS_FILENAME: 'perpsv2-markets.json', AST_FILENAME: 'asts.json', ZERO_ADDRESS: '0x' + '0'.repeat(40), ZERO_BYTES32: '0x' + '0'.repeat(64), inflationStartTimestampInSecs: 1551830400, // 2019-03-06T00:00:00+00:00 }; const knownAccounts = { mainnet: [ { name: 'binance', // Binance 8 Wallet address: '0xF977814e90dA44bFA03b6295A0616a897441aceC', }, { name: 'renBTCWallet', // KeeperDAO wallet (has renBTC and ETH) address: '0x35ffd6e268610e764ff6944d07760d0efe5e40e5', }, { name: 'loansAccount', address: '0x62f7A1F94aba23eD2dD108F8D23Aa3e7d452565B', }, ], }; // The solidity defaults are managed here in the same format they will be stored, hence all // numbers are converted to strings and those with 18 decimals are also converted to wei amounts const defaults = { TEMP_OWNER_DEFAULT_DURATION: 60 * 60 * 24 * 60, // 60 days WAITING_PERIOD_SECS: (60 * 5).toString(), // 5 mins PRICE_DEVIATION_THRESHOLD_FACTOR: w3utils.toWei('3'), TRADING_REWARDS_ENABLED: false, ISSUANCE_RATIO: w3utils .toBN(1) .mul(w3utils.toBN(1e18)) .div(w3utils.toBN(3)) .toString(), // 1/3 = 0.3333333333 // 300% ratio FEE_PERIOD_DURATION: (3600 * 24 * 7).toString(), // 1 week TARGET_THRESHOLD: '1', // 1% target threshold (it will be converted to a decimal when set) LIQUIDATION_DELAY: (3600 * 24).toString(), // 24 hours LIQUIDATION_RATIO: w3utils .toBN(1) .mul(w3utils.toBN(2e18)) .div(w3utils.toBN(3)) .toString(), // 2/3 = 0.6666666667 // 150% ratio LIQUIDATION_ESCROW_DURATION: (3600 * 24 * 365).toString(), // 1 year LIQUIDATION_PENALTY: w3utils.toWei('0.1'), // 10% penalty (used for Collateral liquidations) SNX_LIQUIDATION_PENALTY: w3utils.toWei('0.3'), // 30% penalty (used for SNX Liquidations) SELF_LIQUIDATION_PENALTY: w3utils.toWei('0.2'), // 20% penalty FLAG_REWARD: w3utils.toWei('10'), // 10 SNX LIQUIDATE_REWARD: w3utils.toWei('20'), // 20 SNX RATE_STALE_PERIOD: (3600 * 25).toString(), // 25 hours EXCHANGE_FEE_RATES: { forex: w3utils.toWei('0.003'), commodity: w3utils.toWei('0.003'), equities: w3utils.toWei('0.003'), crypto: w3utils.toWei('0.01'), index: w3utils.toWei('0.01'), }, EXCHANGE_DYNAMIC_FEE_THRESHOLD: w3utils.toWei('0.0025'), EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY: w3utils.toWei('0.95'), // dynamic fee weight decay for each round EXCHANGE_DYNAMIC_FEE_ROUNDS: '6', // dynamic fee rounds EXCHANGE_MAX_DYNAMIC_FEE: w3utils.toWei('0.015'), // cap max dynamic fee MINIMUM_STAKE_TIME: (3600 * 24).toString(), // 1 days DEBT_SNAPSHOT_STALE_TIME: (43800).toString(), // 12 hour heartbeat + 10 minutes mining time AGGREGATOR_WARNING_FLAGS: { mainnet: '0x4A5b9B4aD08616D11F3A402FF7cBEAcB732a76C6', }, RENBTC_ERC20_ADDRESSES: { mainnet: '0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D', // Adding zero addresses here - we don't actually support renBTC anymore. 'mainnet-ovm': '0x0000000000000000000000000000000000000000', sepolia: '0x0000000000000000000000000000000000000000', 'sepolia-ovm': '0x0000000000000000000000000000000000000000', }, WETH_ERC20_ADDRESSES: { mainnet: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', sepolia: '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', 'mainnet-ovm': '0x4200000000000000000000000000000000000006', 'sepolia-ovm': '0x4200000000000000000000000000000000000006', }, INITIAL_ISSUANCE: w3utils.toWei(`${100e6}`), CROSS_DOMAIN_DEPOSIT_GAS_LIMIT: `${3e6}`, CROSS_DOMAIN_ESCROW_GAS_LIMIT: `${8e6}`, CROSS_DOMAIN_REWARD_GAS_LIMIT: `${8e6}`, CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT: `${3e6}`, CROSS_DOMAIN_RELAY_GAS_LIMIT: `${8e6}`, CROSS_DOMAIN_FEE_PERIOD_CLOSE_GAS_LIMIT: `${8e6}`, COLLATERAL_MANAGER: { SYNTHS: ['sUSD', 'sBTC', 'sETH'], SHORTS: ['sBTC', 'sETH'], MAX_DEBT: w3utils.toWei('75000000'), // 75 million sUSD MAX_SKEW_RATE: w3utils.toWei('0.2'), BASE_BORROW_RATE: Math.round((0.005 * 1e18) / 31556926).toString(), // 31556926 is CollateralManager seconds per year BASE_SHORT_RATE: Math.round((0.005 * 1e18) / 31556926).toString(), }, COLLATERAL_ETH: { SYNTHS: ['sUSD', 'sETH'], MIN_CRATIO: w3utils.toWei('1.3'), MIN_COLLATERAL: w3utils.toWei('2'), ISSUE_FEE_RATE: w3utils.toWei('0.001'), }, COLLATERAL_RENBTC: { SYNTHS: ['sUSD', 'sBTC'], MIN_CRATIO: w3utils.toWei('1.3'), MIN_COLLATERAL: w3utils.toWei('0.05'), ISSUE_FEE_RATE: w3utils.toWei('0.001'), }, COLLATERAL_SHORT: { SYNTHS: ['sBTC', 'sETH'], MIN_CRATIO: w3utils.toWei('1.2'), MIN_COLLATERAL: w3utils.toWei('1000'), ISSUE_FEE_RATE: w3utils.toWei('0.005'), INTERACTION_DELAY: '3600', // 1 hour in secs COLLAPSE_FEE_RATE: '0', }, ETHER_WRAPPER_MAX_ETH: w3utils.toWei('5000'), ETHER_WRAPPER_MINT_FEE_RATE: w3utils.toWei('0.005'), // 5 bps ETHER_WRAPPER_BURN_FEE_RATE: '0', FUTURES_MIN_KEEPER_FEE: w3utils.toWei('1'), // 1 sUSD min keeper fee FUTURES_MAX_KEEPER_FEE: w3utils.toWei('1000'), // 1000 sUSD min keeper fee FUTURES_LIQUIDATION_FEE_RATIO: w3utils.toWei('0.0035'), // 35 basis points liquidation incentive FUTURES_LIQUIDATION_BUFFER_RATIO: w3utils.toWei('0.0025'), // 25 basis points liquidation buffer FUTURES_MIN_INITIAL_MARGIN: w3utils.toWei('40'), // minimum initial margin for all markets PERPSV2_KEEPER_LIQUIDATION_FEE: w3utils.toWei('2'), // 2 sUSD keeper liquidation fee (not flagger) // SIP-120 ATOMIC_MAX_VOLUME_PER_BLOCK: w3utils.toWei(`${2e5}`), // 200k ATOMIC_TWAP_WINDOW: '1800', // 30 mins }; /** * Converts a string into a hex representation of bytes32, with right padding */ const toBytes32 = key => w3utils.rightPad(w3utils.asciiToHex(key), 64); const fromBytes32 = key => w3utils.hexToAscii(key); const getFolderNameForNetwork = ({ network, useOvm = false }) => { if (network.includes('ovm')) { return network; } return useOvm ? `${network}-ovm` : network; }; const getPathToNetwork = ({ network = 'mainnet', file = '', useOvm = false, path } = {}) => path.join(__dirname, 'publish', 'deployed', getFolderNameForNetwork({ network, useOvm }), file); // Pass in fs and path to avoid webpack wrapping those const loadDeploymentFile = ({ network = 'mainnet', path, fs, deploymentPath, useOvm = false }) => { if (!deploymentPath && (!path || !fs)) { return data[getFolderNameForNetwork({ network, useOvm })].deployment; } const pathToDeployment = deploymentPath ? path.join(deploymentPath, constants.DEPLOYMENT_FILENAME) : getPathToNetwork({ network, useOvm, path, file: constants.DEPLOYMENT_FILENAME }); if (!fs.existsSync(pathToDeployment)) { throw Error(`Cannot find deployment for network: ${network}.`); } return JSON.parse(fs.readFileSync(pathToDeployment)); }; /** * Retrieve the list of targets for the network - returning the name, address, source file and link to etherscan */ const getTarget = ({ network = 'mainnet', useOvm = false, contract, path, fs, deploymentPath, } = {}) => { const deployment = loadDeploymentFile({ network, useOvm, path, fs, deploymentPath }); if (contract) return deployment.targets[contract]; else return deployment.targets; }; /** * Retrieve the list of solidity sources for the network - returning the abi and bytecode */ const getSource = ({ network = 'mainnet', useOvm = false, contract, path, fs, deploymentPath, } = {}) => { const deployment = loadDeploymentFile({ network, useOvm, path, fs, deploymentPath }); if (contract) return deployment.sources[contract]; else return deployment.sources; }; /** * Retrieve the ASTs for the source contracts */ const getAST = ({ source, path, fs, match = /^contracts\// } = {}) => { let fullAST; if (path && fs) { const pathToAST = path.resolve( __dirname, constants.BUILD_FOLDER, constants.AST_FOLDER, constants.AST_FILENAME ); if (!fs.existsSync(pathToAST)) { throw Error('Cannot find AST'); } fullAST = JSON.parse(fs.readFileSync(pathToAST)); } else { // Note: The below cannot be required as the build folder is not stored // in code (only in the published module). // The solution involves tracking these after each commit in another file // somewhere persisted in the codebase - JJM // data.ast = require('./build/ast/asts.json'), if (!data.ast) { throw Error('AST currently not supported in browser mode'); } fullAST = data.ast; } // remove anything not matching the pattern const ast = Object.entries(fullAST) .filter(([astEntryKey]) => match.test(astEntryKey)) .reduce((memo, [key, val]) => { memo[key] = val; return memo; }, {}); if (source && source in ast) { return ast[source]; } else if (source) { // try to find the source without a path const [key, entry] = Object.entries(ast).find(([astEntryKey]) => astEntryKey.includes('/' + source)) || []; if (!key || !entry) { throw Error(`Cannot find AST entry for source: ${source}`); } return { [key]: entry }; } else { return ast; } }; const getFeeds = ({ network, path, fs, deploymentPath, useOvm = false } = {}) => { let feeds; if (!deploymentPath && (!path || !fs)) { feeds = data[getFolderNameForNetwork({ network, useOvm })].feeds; } else { const pathToFeeds = deploymentPath ? path.join(deploymentPath, constants.FEEDS_FILENAME) : getPathToNetwork({ network, path, useOvm, file: constants.FEEDS_FILENAME, }); if (!fs.existsSync(pathToFeeds)) { throw Error(`Cannot find feeds file.`); } feeds = JSON.parse(fs.readFileSync(pathToFeeds)); } // now mix in the asset data return Object.entries(feeds).reduce((memo, [asset, entry]) => { memo[asset] = Object.assign(assets[asset], entry); return memo; }, {}); }; const getOffchainFeeds = ({ network, path, fs, deploymentPath, useOvm = false } = {}) => { if (!deploymentPath && (!path || !fs)) { return data[getFolderNameForNetwork({ network, useOvm })].offchainFeeds; } else { const pathToFeeds = deploymentPath ? path.join(deploymentPath, constants.OFFCHAIN_FEEDS_FILENAME) : getPathToNetwork({ network, path, useOvm, file: constants.OFFCHAIN_FEEDS_FILENAME, }); if (!fs.existsSync(pathToFeeds)) { throw Error(`Cannot find off-chain feeds file.`); } return JSON.parse(fs.readFileSync(pathToFeeds)); } }; /** * Retrieve ths list of synths for the network - returning their names, assets underlying, category, sign, description, and * optional index and inverse properties */ const getSynths = ({ network = 'mainnet', path, fs, deploymentPath, useOvm = false, skipPopulate = false, } = {}) => { let synths; if (!deploymentPath && (!path || !fs)) { synths = data[getFolderNameForNetwork({ network, useOvm })].synths; } else { const pathToSynthList = deploymentPath ? path.join(deploymentPath, constants.SYNTHS_FILENAME) : getPathToNetwork({ network, useOvm, path, file: constants.SYNTHS_FILENAME }); if (!fs.existsSync(pathToSynthList)) { throw Error(`Cannot find synth list.`); } synths = JSON.parse(fs.readFileSync(pathToSynthList)); } if (skipPopulate) { return synths; } const feeds = getFeeds({ network, useOvm, path, fs, deploymentPath }); // copy all necessary index parameters from the longs to the corresponding shorts return synths.map(synth => { // mixin the asset details synth = Object.assign({}, assets[synth.asset], synth); if (feeds[synth.asset]) { const { feed } = feeds[synth.asset]; synth = Object.assign({ feed }, synth); } // replace an index placeholder with the index details if (typeof synth.index === 'string') { const { index } = synths.find(({ name }) => name === synth.index) || {}; if (!index) { throw Error( `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` ); } synth = Object.assign({}, synth, { index }); } if (synth.index) { synth.index = synth.index.map(indexEntry => { return Object.assign({}, assets[indexEntry.asset], indexEntry); }); } return synth; }); }; const getFuturesMarkets = ({ network = 'mainnet', useOvm = false, path, fs, deploymentPath, } = {}) => { let futuresMarkets; if (!deploymentPath && (!path || !fs)) { futuresMarkets = data[getFolderNameForNetwork({ network, useOvm })].futuresMarkets; } else { const pathToFuturesMarketsList = deploymentPath ? path.join(deploymentPath, constants.FUTURES_MARKETS_FILENAME) : getPathToNetwork({ network, path, useOvm, file: constants.FUTURES_MARKETS_FILENAME, }); if (!fs.existsSync(pathToFuturesMarketsList)) { futuresMarkets = []; } else { futuresMarkets = JSON.parse(fs.readFileSync(pathToFuturesMarketsList)) || []; } } return futuresMarkets.map(futuresMarket => { /** * We expect the asset key to not start with an 's'. ie. AVAX rather than sAVAX * Unfortunately due to some historical reasons 'sBTC', 'sETH' and 'sLINK' does not follow this format * We adjust for that here. */ const marketsWithIncorrectAssetKey = ['sBTC', 'sETH', 'sLINK']; const assetKeyNeedsAdjustment = marketsWithIncorrectAssetKey.includes(futuresMarket.asset); const assetKey = assetKeyNeedsAdjustment ? futuresMarket.asset.slice(1) : futuresMarket.asset; // mixin the asset details return Object.assign({}, assets[assetKey], futuresMarket); }); }; const getPerpsMarkets = ({ network = 'mainnet', useOvm = false, path, fs, deploymentPath, } = {}) => { let perpsMarkets; if (!deploymentPath && (!path || !fs)) { perpsMarkets = data[getFolderNameForNetwork({ network, useOvm })].perpsv2Markets; } else { const pathToPerpsMarketsList = deploymentPath ? path.join(deploymentPath, constants.PERPS_V2_MARKETS_FILENAME) : getPathToNetwork({ network, path, useOvm, file: constants.PERPS_V2_MARKETS_FILENAME, }); if (!fs.existsSync(pathToPerpsMarketsList)) { perpsMarkets = []; } else { perpsMarkets = JSON.parse(fs.readFileSync(pathToPerpsMarketsList)) || []; } } return perpsMarkets.map(perpsMarket => { /** * We expect the asset key to not start with an 's'. ie. AVAX rather than sAVAX * Unfortunately due to some historical reasons 'sBTC' and 'sETH' does not follow this format * We adjust for that here. */ const marketsWithIncorrectAssetKey = ['sBTC', 'sETH']; const assetKeyNeedsAdjustment = marketsWithIncorrectAssetKey.includes(perpsMarket.asset); const assetKey = assetKeyNeedsAdjustment ? perpsMarket.asset.slice(1) : perpsMarket.asset; // mixin the asset details return Object.assign({}, assets[assetKey], perpsMarket); }); }; const getPerpsV2ProxiedMarkets = ({ network = 'mainnet', fs, deploymentPath, path }) => { const _analyzeAndIncludePerpsV2 = (target, targetData, sourceData, PerpsV2Proxied) => { const proxyPrefix = 'PerpsV2Proxy'; const marketPrefix = 'PerpsV2Market'; const excludedContracts = ['PerpsV2MarketSettings', 'PerpsV2MarketData', 'PerpsV2ExchangeRate']; const excludedLegacyContracts = ['PerpsV2DelayedOrder', 'PerpsV2OffchainDelayedOrder']; const prefixes = [ 'PerpsV2MarketViews', 'PerpsV2DelayedIntent', 'PerpsV2DelayedExecution', 'PerpsV2MarketLiquidate', ]; if ( excludedContracts.includes(target) || target.startsWith('PerpsV2MarketState') || excludedLegacyContracts.some(prefix => target.startsWith(prefix)) ) { // Markets helper or Market state. Do nothing return; } // If is the proxy, get the address. Initialize object if not done yet if (target.startsWith(proxyPrefix)) { // get name const marketName = target.slice(proxyPrefix.length); if (!PerpsV2Proxied[marketName]) { PerpsV2Proxied[marketName] = {}; PerpsV2Proxied[marketName].abi = []; } // get address PerpsV2Proxied[marketName].address = targetData.address; } else { // Not proxy, is one of the components. First try with the long contract names because main component prefix is included in others let nameFound = false; let marketName; // Identify the market name (after the prefix) for (const prefix of prefixes) { if (target.startsWith(prefix)) { // get name marketName = target.slice(prefix.length); nameFound = true; } } // if not found one the previous step, it should be PerpsV2MarketXXXXX if (!nameFound) { if (target.startsWith(marketPrefix)) { // get name marketName = target.slice(marketPrefix.length); nameFound = true; } } if (nameFound) { // Initialize if not done yet if (!PerpsV2Proxied[marketName]) { PerpsV2Proxied[marketName] = {}; PerpsV2Proxied[marketName].abi = []; } // add fragments to abi _consolidateAbi(sourceData.abi, PerpsV2Proxied[marketName].abi); } } }; const _consolidateAbi = (currentAbi, consolidatedAbi) => { for (const abiFragment of currentAbi) { if ( !consolidatedAbi.find( f => f.type === abiFragment.type && f.name && abiFragment.name && f.name === abiFragment.name ) ) { if (abiFragment.type !== 'constructor') { // don't push constructors to the consolidated abi consolidatedAbi.push(abiFragment); } } } }; const deploymentData = loadDeploymentFile({ network, useOvm: false, path, fs, deploymentPath }); const targets = Object.keys(deploymentData.targets); const PerpsV2Proxied = {}; for (const target of targets) { if (!target.startsWith('PerpsV2')) { continue; } const targetData = getTarget({ contract: target, network, useOvm: false, path, fs, deploymentPath, }); const sourceData = getSource({ contract: targetData.source, network, useOvm: false, path, fs, deploymentPath, }); _analyzeAndIncludePerpsV2(target, targetData, sourceData, PerpsV2Proxied); } return PerpsV2Proxied; }; /** * Retrieve the list of staking rewards for the network - returning this names, stakingToken, and rewardToken */ const getStakingRewards = ({ network = 'mainnet', useOvm = false, path, fs, deploymentPath, } = {}) => { if (!deploymentPath && (!path || !fs)) { return data[getFolderNameForNetwork({ network, useOvm })].rewards; } const pathToStakingRewardsList = deploymentPath ? path.join(deploymentPath, constants.STAKING_REWARDS_FILENAME) : getPathToNetwork({ network, path, useOvm, file: constants.STAKING_REWARDS_FILENAME, }); if (!fs.existsSync(pathToStakingRewardsList)) { return []; } return JSON.parse(fs.readFileSync(pathToStakingRewardsList)); }; /** * Retrieve the list of shorting rewards for the network - returning the names and rewardTokens */ const getShortingRewards = ({ network = 'mainnet', useOvm = false, path, fs, deploymentPath, } = {}) => { if (!deploymentPath && (!path || !fs)) { return data[getFolderNameForNetwork({ network, useOvm })]['shorting-rewards']; } const pathToShortingRewardsList = deploymentPath ? path.join(deploymentPath, constants.SHORTING_REWARDS_FILENAME) : getPathToNetwork({ network, path, useOvm, file: constants.SHORTING_REWARDS_FILENAME, }); if (!fs.existsSync(pathToShortingRewardsList)) { return []; } return JSON.parse(fs.readFileSync(pathToShortingRewardsList)); }; /** * Retrieve the list of system user addresses */ const getUsers = ({ network = 'mainnet', user, useOvm = false } = {}) => { const testnetOwner = '0x48914229deDd5A9922f44441ffCCfC2Cb7856Ee9'; const base = { owner: testnetOwner, deployer: testnetOwner, marketClosure: testnetOwner, oracle: '0xac1e8B385230970319906C03A1d8567e3996d1d5', fee: '0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF', zero: '0x' + '0'.repeat(40), }; const map = { mainnet: Object.assign({}, base, { owner: '0xEb3107117FEAd7de89Cd14D463D340A2E6917769', deployer: '0xEde8a407913A874Dd7e3d5B731AFcA135D30375E', marketClosure: '0xC105Ea57Eb434Fbe44690d7Dec2702e4a2FBFCf7', oracle: '0xaC1ED4Fabbd5204E02950D68b6FC8c446AC95362', }), 'mainnet-ovm': Object.assign({}, base, { owner: '0x6d4a64C57612841c2C6745dB2a4E4db34F002D20', deployer: '0xEde8a407913A874Dd7e3d5B731AFcA135D30375E', }), sepolia: Object.assign({}, base, { owner: '0x48914229deDd5A9922f44441ffCCfC2Cb7856Ee9', deployer: '0x48914229deDd5A9922f44441ffCCfC2Cb7856Ee9', }), 'sepolia-ovm': Object.assign({}, base, { owner: '0x48914229deDd5A9922f44441ffCCfC2Cb7856Ee9', deployer: '0x48914229deDd5A9922f44441ffCCfC2Cb7856Ee9', }), local: Object.assign({}, base, { // Deterministic account #0 when using `npx hardhat node` owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', }), 'local-ovm': Object.assign({}, base, { // Deterministic account #0 when using `npx hardhat node` owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', deployer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', oracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', }), }; const users = Object.entries( map[getFolderNameForNetwork({ network, useOvm })] ).map(([key, value]) => ({ name: key, address: value })); return user ? users.find(({ name }) => name === user) : users; }; const getVersions = ({ network = 'mainnet', path, fs, deploymentPath, useOvm, byContract = false, } = {}) => { let versions; if (!deploymentPath && (!path || !fs)) { versions = data[getFolderNameForNetwork({ network, useOvm })].versions; } else { const pathToVersions = deploymentPath ? path.join(deploymentPath, constants.VERSIONS_FILENAME) : getPathToNetwork({ network, useOvm, path, file: constants.VERSIONS_FILENAME }); if (!fs.existsSync(pathToVersions)) { throw Error(`Cannot find versions for network.`); } versions = JSON.parse(fs.readFileSync(pathToVersions)); } if (byContract) { // compile from the contract perspective return Object.values(versions).reduce( (memo, { tag, release, date, commit, block, contracts }) => { for (const [contract, contractEntry] of Object.entries(contracts)) { memo[contract] = memo[contract] || []; memo[contract].push(Object.assign({ tag, release, date, commit, block }, contractEntry)); } return memo; }, {} ); } return versions; }; const getSuspensionReasons = ({ code = undefined } = {}) => { const suspensionReasonMap = { 1: 'System Upgrade', 2: 'Market Closure', 4: 'iSynth Reprice', 6: 'Index Rebalance', 55: 'Circuit Breaker (Phase one)', // https://sips.synthetix.io/SIPS/sip-55 65: 'Decentralized Circuit Breaker (Phase two)', // https://sips.synthetix.io/SIPS/sip-65 80: 'Futures configuration', // pausing according to deployment configuration 231: 'Latency Breaker', // https://sips.synthetix.io/sips/sip-231/ 99999: 'Emergency', }; return code ? suspensionReasonMap[code] : suspensionReasonMap; }; /** * Retrieve the list of tokens used in the Synthetix protocol */ const getTokens = ({ network = 'mainnet', path, fs, useOvm = false } = {}) => { const synths = getSynths({ network, useOvm, path, fs }); const targets = getTarget({ network, useOvm, path, fs }); const feeds = getFeeds({ network, useOvm, path, fs }); return [ Object.assign( { symbol: 'SNX', asset: 'SNX', name: 'Synthetix', address: targets.ProxySynthetix.address, decimals: 18, }, feeds['SNX'].feed ? { feed: feeds['SNX'].feed } : {} ), ].concat( synths .filter(({ category }) => category !== 'internal') .map(synth => ({ symbol: synth.name, asset: synth.asset, name: synth.description, address: (targets[`Proxy${synth.name}`] || {}).address, index: synth.index, decimals: 18, feed: synth.feed, })) .sort((a, b) => (a.symbol > b.symbol ? 1 : -1)) ); }; const enhanceDecodedData = decoded => { const decodedBytes32 = p => { try { return { ascii: fromBytes32(p).replaceAll('\x00', '') }; } catch (e) { return { ascii: '\\error decoding\\' }; } }; const formatDecimals = number => { const exp = /(\d)(?=(\d{3})+(?!\d))/g; const rep = '$1,'; return number.toString().replace(exp, rep); }; const decodeUint = p => { try { const value = w3utils.toBN(p); return { bp: value.div(w3utils.toBN(1e14)).toString(), decimal: formatDecimals(value.div(w3utils.toBN(1e18)).toString()), number: formatDecimals(value.toString()), }; } catch (e) { return { ascii: '\\error decoding\\' }; } }; const enhancedParams = decoded.method.params.map(p => { if (p.type === 'bytes32') { return { ...p, enhanced: decodedBytes32(p.value) }; } if (p.type === 'bytes32[]') { p.value = p.value.map(original => { return { original, enhanced: decodedBytes32(original) }; }); } if (/u?int[1-3][0-9]?./.test(p.type)) { return { ...p, enhanced: decodeUint(p.value) }; } if (p.type === 'tuple') { const keys = Object.keys(p.value).filter(v => isNaN(v)); const values = []; for (const key of keys) { if (p.value[key].startsWith('0x')) { if (p.value[key].length === 66) { values[key] = { original: p.value[key], enhanced: decodedBytes32(p.value[key]) }; continue; } values[key] = p.value[key]; continue; } values[key] = { original: p.value[key], enhanced: decodeUint(p.value[key]) }; } p.value = values; } return p; }); const enhancedMethod = { ...decoded.method, params: enhancedParams }; return { ...decoded, method: enhancedMethod }; }; const decode = ({ network = 'mainnet', fs, path, data, target, useOvm = false, decodeMigration = false, enhanceDecode = false, } = {}) => { const sources = getSource({ network, path, fs, useOvm }); for (const { abi } of Object.values(sources)) { abiDecoder.addABI(abi); } if (decodeMigration) { abiDecoder.addABI([ { constant: false, inputs: [], name: 'migrate', outputs: [], payable: false, stateMutability: 'nonpayable', type: 'function', }, ]); } const targets = getTarget({ network, path, fs, useOvm }); let contract; if (target) { contract = Object.values(targets).filter( ({ address }) => address.toLowerCase() === target.toLowerCase() )[0].name; } const result = { method: abiDecoder.decodeMethod(data), contract }; return enhanceDecode ? enhanceDecodedData(result) : result; }; const wrap = ({ network, deploymentPath, fs, path, useOvm = false }) => [ 'decode', 'getAST', 'getPathToNetwork', 'getSource', 'getStakingRewards', 'getShortingRewards', 'getFeeds', 'getOffchainFeeds', 'getSynths', 'getTarget', 'getFuturesMarkets', 'getPerpsMarkets', 'getPerpsV2ProxiedMarkets', 'getTokens', 'getUsers', 'getVersions', ].reduce((memo, fnc) => { memo[fnc] = (prop = {}) => module.exports[fnc](Object.assign({ network, deploymentPath, fs, path, useOvm }, prop)); return memo; }, {}); const getNextRelease = ({ useOvm }) => { const release = releases.releases.find(({ released, ovm }) => !released && (useOvm ? ovm : !ovm)); return Object.assign({}, release, { releaseName: release.name.replace(/[^\w]/g, '') }); }; module.exports = { chainIdMapping, constants, decode, defaults, getAST, getNetworkFromId, getNextRelease, getPathToNetwork, getSource, getStakingRewards, getShortingRewards, getSuspensionReasons, getFeeds, getOffchainFeeds, getSynths, getFuturesMarkets, getPerpsMarkets, getPerpsV2ProxiedMarkets, getTarget, getTokens, getUsers, getVersions, networks, networkToChainId, toBytes32, fromBytes32, wrap, nonUpgradeable, releases, knownAccounts, };