Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Bump to m7, Remove Sudo Pallet, Clean up Chain Spec #256

Merged
merged 4 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ lcov.info

# Releases
releases

# Dev Chain
chains/dev/*
15 changes: 0 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 0 additions & 126 deletions alphaTestnetChainSpec.json

This file was deleted.

59 changes: 0 additions & 59 deletions alphaTestnetChainSpecRaw.json

This file was deleted.

230 changes: 230 additions & 0 deletions chains/build_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#!env node
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a script that builds chains/{chain}/chain-spec.js and chains/{chain}/chain-spec-raw.js based on data from chains/{chain}/chain-config.json and other sources (e.g. compound-config and ethereum/networks/ropsten.json)

const child_process = require('child_process');
const fs = require('fs').promises;
const path = require('path');
const getopts = require('getopts');
const chalk = require('chalk');
const types = require('@polkadot/types');
const Contract = require('web3-eth-contract');

function getEthProvider(network) {
if (['ropsten'].includes(network)) {
return `https://${network}-eth.compound.finance`;
} else {
throw new Error(`Unknown or unsupported eth network: "${network}"`);
}
}

async function readFile(chain, file) {
return await fs.readFile(path.join(__dirname, chain, file), 'utf8');
}

async function writeFile(chain, file, contents) {
let filePath = path.join(__dirname, chain, file);
await fs.writeFile(filePath, contents);
return filePath;
}

async function fetchCompoundConfig(network) {
return JSON.parse(await fs.readFile(path.join(__dirname, 'node_modules', 'compound-config', 'networks', `${network}.json`), 'utf8'));
}

async function fetchChainDeployment(network) {
return JSON.parse(await fs.readFile(path.join(__dirname, '..', 'ethereum','networks', `${network}.json`), 'utf8'));
}

async function fetchChainDeploymentABI(network) {
return JSON.parse(await fs.readFile(path.join(__dirname, '..', 'ethereum','networks', `${network}-abi.json`), 'utf8'));
}

function exec(target, args, opts = {}) {
return new Promise((resolve, reject) => {
let proc = child_process.spawn(target, args, opts);
let res = "";

proc.stdout.on('data', (data) => {
res += data;
});

proc.stderr.on('data', (data) => {
console.log(`[${target}]: ${data}`);
});

proc.on('close', (code) => {
if (code === 0) {
resolve(res);
} else {
reject(`Process \`${target}\` exited with error code: ${code}`);
}
});
});
}

async function buildGateway() {
console.log(chalk.yellow("Building Gateway release build..."));
return await exec("cargo", ["build", "--release"]);
}

async function execGateway(args) {
console.log(chalk.yellow(`Running Gateway with args ${JSON.stringify(args)}...`));
let bin = process.env['CHAIN_BIN'] || path.join(__dirname, '..', 'target', 'release', 'gateway');
return await exec(bin, args);
}

async function setAuthorities(chainSpec, chainConfig, opts) {
let registry = new types.TypeRegistry();
let validators = chainConfig.validators.map(({substrate_id, eth_address}) => {
let substrateId = (new types.GenericAccountId(registry, substrate_id));

return {
substrate_id: [...substrateId.toU8a()],
eth_address
};
});

let sessionKeys = chainConfig.validators.filter((s) => s.hasOwnProperty('session_keys')).map(({substrate_id, session_keys}) => {
return [
substrate_id,
substrate_id,
session_keys
];
});

chainSpec.genesis.runtime.palletCash.validators = validators;
chainSpec.genesis.runtime.palletSession.keys = sessionKeys;
}

async function setStarport(chainSpec, chainConfig, opts) {
let chainDeployment = await fetchChainDeployment(chainConfig.eth_network);
let starportAddress = must(chainDeployment.Contracts, 'Starport');
chainSpec.properties.eth_starport_address = starportAddress;
}

async function setInitialYield(chainSpec, chainConfig, opts) {
let chainDeployment = await fetchChainDeployment(chainConfig.eth_network);
let chainDeploymentABI = await fetchChainDeploymentABI(chainConfig.eth_network);
let cashTokenAddress = chainDeployment.Contracts.Cash;
if (!cashTokenAddress) {
throw new Error(`Missing cash token address for network ${chainConfig.eth_network}`);
}
let cashTokenABI = chainDeploymentABI.Cash;
if (!cashTokenABI) {
throw new Error(`Missing cash token ABI for network ${chainConfig.eth_network}`);
}
let cashToken = new Contract(cashTokenABI, cashTokenAddress);
cashToken.setProvider(getEthProvider(chainConfig.eth_network));
let yieldStart = await cashToken.methods.cashYieldStart().call();
let cashYield = (await cashToken.methods.cashYieldAndIndex().call()).yield;

chainSpec.genesis.runtime.palletCash.lastYieldTimestamp = Number(yieldStart) * 1000;
chainSpec.genesis.runtime.palletCash.cashYield = Number(cashYield);
}

function must(hash, key, validator = (x) => x !== undefined) {
if (hash.hasOwnProperty(key) || validator(undefined)) {
let val = hash[key];
if (!validator(val)) {
throw new Error(`Error validating ${key} with val ${val}`);
}
return val;
} else {
throw new Error(`Could not find key ${key} for object with keys ${JSON.stringify(Object.keys(hash))}`);
}
}

function upper(obj) {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k.toUpperCase(), v]))
}

async function setAssetInfo(chainSpec, chainConfig, opts) {
let compoundConfig = await fetchCompoundConfig(chainConfig.eth_network);
let contracts = upper(compoundConfig['Contracts']);
let tokenInfos = upper(compoundConfig['Tokens']);

let rateModels = chainConfig.rate_models;
let assets = Object.entries(chainConfig.tokens).map(([symbol, info]) => {
let tokenAddress = contracts[symbol];
if (!tokenAddress) {
throw new Error(`Missing contract address on ${chainConfig.eth_network} for ${symbol} from compound-config`);
}

let tokenInfo = tokenInfos[symbol];
if (!tokenInfo) {
throw new Error(`Missing token info on ${chainConfig.eth_network} for ${symbol} from compound-config`);
}
let asset = `ETH:${tokenAddress}`;
let decimals = must(tokenInfo, 'decimals', (d) => typeof(d) === 'number');
let liquidity_factor = must(info, 'liquidity_factor', (d) => typeof(d) === 'number' && d > 0 && d <= 1) * 1e18;
let rate_model;
let rateModelRaw = must(info, 'rate_model');
if (typeof(rateModelRaw) === 'string') {
rate_model = rateModels[rateModelRaw];
if (!rate_model) {
throw new Error(`Unknown or missing rate model: ${rate_model}`)
}
} else {
rate_model = rateModelRaw;
}
let miner_shares = must(info, 'miner_shares', (d) => typeof(d) === 'number' && d >= 0 && d <= 1) * 1e18;
let supply_cap = must(info, 'supply_cap', (d) => typeof(d) === 'undefined' || typeof(d) === 'number') || 0;
let ticker = must(info, 'ticker', (d) => typeof(d) === 'undefined' || typeof(d) === 'string') || symbol;

return {
asset,
decimals,
liquidity_factor,
rate_model,
miner_shares,
supply_cap,
symbol,
ticker
};
});

chainSpec.genesis.runtime.palletCash.assets = assets;
}

async function setReporters(chainSpec, chainConfig, opts) {
chainSpec.genesis.runtime.palletOracle.reporters = chainConfig.reporters;
}

async function buildSpec(opts) {
if (!opts.skip_build) {
await buildGateway();
}
let chain = opts.chain;

let chainConfig = JSON.parse(await readFile(chain, 'chain-config.json'));
let chainSpec = JSON.parse(await execGateway(["build-spec", "--disable-default-bootnode", "--chain", chainConfig.base_chain]));
await setAuthorities(chainSpec, chainConfig, opts);
await setAssetInfo(chainSpec, chainConfig, opts);
await setReporters(chainSpec, chainConfig, opts);
await setStarport(chainSpec, chainConfig, opts);
await setInitialYield(chainSpec, chainConfig, opts);

await writeFile(chain, 'chain-spec.json', JSON.stringify(chainSpec, null, 2));

let chainSpecFile = await writeFile(chain, 'chain-spec.json', JSON.stringify(chainSpec, null, 2));

// Next, build raw spec
let raw = JSON.parse(await execGateway(["build-spec", "--chain", chainSpecFile, "--raw", "--disable-default-bootnode"]));

// Next, build
await writeFile(chain, 'chain-spec-raw.json', JSON.stringify(raw, null, 2));
}

const options = getopts(process.argv.slice(2), {
alias: {
chain: "c",
skip_build: "s"
}
});

if (!options.chain) {
throw new Error(`Must choose chain with -c`);
}

buildSpec({
skip_build: false,
...options
});
13 changes: 13 additions & 0 deletions chains/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "chains",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@polkadot/types": "^4.1.1",
"chalk": "^4.1.0",
"compound-config": "https://github.com/compound-finance/compound-config.git",
"getopts": "^2.3.0",
"web3-eth-contract": "^1.3.4"
}
}
78 changes: 78 additions & 0 deletions chains/testnet/chain-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the chain-specific info used to build the chain-spec. I try to limit the data that needs to be here, e.g. by pulling the actual token config from compound-chain/networks/ropsten.json where possible

"eth_network": "ropsten",
"base_chain": "testnet",
"validators": [
{
"substrate_id": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"eth_address": "0x55413a2d4908d130c908ccf2f298b235bacd427a",
"session_keys": {
"aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"grandpa": "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
}
}
],
"reporters": [
"0xfceadafab14d46e20144f48824d0c09b1a03f2bc"
],
"rate_models": {
"Kink_500_2000": {
"Kink": {
"zero_rate": 0,
"kink_rate": 500,
"kink_utilization": 8000,
"full_rate": 2000
}
}
},
"tokens": {
"ZRX": {
"liquidity_factor": 0.5,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"WBTC": {
"ticker": "BTC",
"liquidity_factor": 0.6,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"USDT": {
"ticker": "USD",
"liquidity_factor": 0.7,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"USDC": {
"ticker": "USD",
"liquidity_factor": 0.8,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"DAI": {
"ticker": "DAI",
"liquidity_factor": 0.7,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"BAT": {
"liquidity_factor": 0.6,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"COMP": {
"liquidity_factor": 0.8,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"UNI": {
"liquidity_factor": 0.8,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
},
"ETH": {
"liquidity_factor": 0.8,
"rate_model": "Kink_500_2000",
"miner_shares": 0.01
}
}
}
Loading