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

Commit ab85ca1

Browse files
authored
Add Scen Repl (#389)
Try `yarn repl -i ./repl/testnet.json` to connect a new node to Ropsten. You can reference in the repl anything available on ctx, such as `(await api.query.cash....).toJSON()`, etc etc. Code is very rough right now to test that it might work and how.
1 parent 4383cbd commit ab85ca1

File tree

5 files changed

+417
-2
lines changed

5 files changed

+417
-2
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ node_modules
2626
integration/.repl_history
2727
integration/chainSpec.json
2828
integration/*.xml
29+
integration/repl.log
2930

3031
# Builds Ethereum contracts
3132
ethereum/.build/contracts.json

Diff for: integration/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"build": "yarn build:ethereum && yarn build:cargo",
2323
"build:ethereum": "(cd ../ethereum && yarn && yarn compile)",
2424
"build:cargo": "WASM_BUILD_RUSTFLAGS='--cfg feature=\"integration\"' cargo build --release --features integration",
25-
"console": "NODE_OPTIONS='--experimental-repl-await' npx saddle console"
25+
"console": "NODE_OPTIONS='--experimental-repl-await' npx saddle console",
26+
"repl": "NODE_OPTIONS='--experimental-repl-await --experimental-vm-modules' node repl.js"
2627
},
2728
"resolutions": {
2829
"solidity-parser-antlr": "https://github.com/solidity-parser/parser.git",

Diff for: integration/repl.js

+376
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
const fs = require('fs').promises;
2+
const repl = require('repl');
3+
const path = require('path');
4+
const { Readable } = require('stream');
5+
const { readFileSync, createReadStream, constants } = require('fs');
6+
const getopts = require('getopts');
7+
const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api');
8+
const Types = require('@polkadot/types');
9+
const fetch = require('node-fetch');
10+
const chalk = require('chalk');
11+
const { buildCtx } = require('./util/scenario/ctx');
12+
13+
async function fileExists(path) {
14+
try {
15+
await fs.access(path, constants.R_OK);
16+
return true;
17+
} catch (e) {
18+
return false;
19+
}
20+
}
21+
22+
function matchesLine(completion, line) {
23+
if (completion.initial && line.startsWith(completion.initial)) {
24+
return true;
25+
}
26+
27+
return false;
28+
}
29+
30+
function targetMatches(completion, line) {
31+
let words = line.split(/\s+/);
32+
let position = words.length - 1; // e.g. "deploy" = 0 "deploy " = 1 "deploy abc" = 1
33+
let lastWord = words.length === 0 ? "" : words[words.length - 1];
34+
let targets = completion.targets.filter(({pos}) => pos === position);
35+
36+
let matching = targets.reduce((acc, {choices}) => {
37+
return [
38+
...acc,
39+
...choices.filter((choice) => choice.startsWith(lastWord))
40+
];
41+
}, []);
42+
43+
if (lastWord.length > 0) {
44+
return [matching, lastWord];
45+
} else {
46+
return [matching, line];
47+
}
48+
}
49+
50+
function getCompleter(defaultCompleter, completions) {
51+
return function(line, callback) {
52+
const lineMatches = completions.filter((completion) => matchesLine(completion, line));
53+
let [choices, text] = lineMatches.reduce(([accMatch, accText], completion) => {
54+
let [matches, text] = targetMatches(completion, line);
55+
56+
if (matches && text.length < accText.length) {
57+
return [matches, text];
58+
} else if (matches && text.length === accText.length) {
59+
return [ [ ...accMatch, ...matches ], accText];
60+
} else {
61+
return [accMatch, accText];
62+
}
63+
}, [[], line]);
64+
65+
if (choices.length > 0) {
66+
callback(null, [choices, text]);
67+
} else {
68+
defaultCompleter(line, callback);
69+
}
70+
}
71+
}
72+
73+
function getCompletions(defaultCompleter, contracts) {
74+
let contractNames = Object.keys(contracts)
75+
let contractAddresses = Object.values(contracts).filter((x) => !!x);
76+
77+
const completions = [
78+
{
79+
initial: '.deploy',
80+
targets: [
81+
{
82+
pos: 1,
83+
choices: contractNames
84+
}
85+
]
86+
},
87+
{
88+
initial: '.match',
89+
targets: [
90+
{
91+
pos: 1,
92+
choices: contractAddresses
93+
},
94+
{
95+
pos: 2,
96+
choices: contractNames
97+
}
98+
]
99+
}
100+
];
101+
102+
return getCompleter(defaultCompleter, completions);
103+
}
104+
105+
function lowerCase(str) {
106+
if (str === "") {
107+
return "";
108+
} else {
109+
return str[0].toLowerCase() + str.slice(1,);
110+
}
111+
}
112+
113+
async function wrapError(fn, r) {
114+
try {
115+
return await fn;
116+
} catch (err) {
117+
console.error(`Error: ${err}`);
118+
} finally {
119+
r.displayPrompt();
120+
}
121+
}
122+
123+
// async function getContracts(saddle) {
124+
// let contracts = await saddle.listContracts();
125+
// let contractInsts = await Object.entries(contracts).reduce(async (acc, [contract, address]) => {
126+
// if (address) {
127+
// return {
128+
// ... await acc,
129+
// [contract]: await saddle.getContractAt(contract, address)
130+
// };
131+
// } else {
132+
// return await acc;
133+
// }
134+
// }, {});
135+
136+
// return {
137+
// contracts,
138+
// contractInsts
139+
// };
140+
// }
141+
142+
function defineAction(r, fn) {
143+
return async (name) => {
144+
r.clearBufferedCommand();
145+
await wrapError(fn(name), r);
146+
};
147+
}
148+
149+
// function defineCommands(r, { api, keyring, types }, saddle, network, contracts) {
150+
// r.defineCommand('validators', {
151+
// help: 'Show current validators',
152+
// action: defineAction(r, async () => {
153+
// let validators = await api.query.cash.validators.entries();
154+
// validators.forEach(([substrateId, validatorKeys]) => {
155+
// let key = toSS58(keyring, substrateId.toHuman()[0]);
156+
// let value = Object.entries(validatorKeys.unwrap().toJSON()).map(([k, v]) =>
157+
// `\t\t${k}=${v}`).join("\n");
158+
// console.log(`\t${key}:\n${value}\n`);
159+
// });
160+
// })
161+
// });
162+
163+
// r.defineCommand('decode_call', {
164+
// help: 'Decode a call',
165+
// action: defineAction(r, async (name) => {
166+
// let call = new types.GenericCall(api.registry, name);
167+
// let { method, section, args } = call.toHuman();
168+
// console.log(`Extrinsic Call:\n\t${section}.${method}(${args.map((a) => JSON.stringify(a)).join(",")})`);
169+
// })
170+
// });
171+
172+
// r.defineCommand('block', {
173+
// help: 'Show current gateway block',
174+
// action: defineAction(r, async () => {
175+
// const blockHash = await api.rpc.chain.getBlockHash();
176+
// const signedBlock = await api.rpc.chain.getBlock(blockHash);
177+
// let header = signedBlock.block.header;
178+
// console.log(`#${header.number}`);
179+
// })
180+
// });
181+
182+
// r.defineCommand('exec', {
183+
// help: 'Sign and send a trx request from saddle eth addr',
184+
// action: defineAction(r, async (request) => {
185+
// let user = saddle.account;// get saddle user and make chain account
186+
// const nonce = await api.query.cash.nonces({eth: user});
187+
// let req = `${nonce}:${request}`
188+
// let sig = await saddle.web3.eth.sign(req, user)
189+
// console.log("🎲", req)
190+
// let tx = api.tx.cash.execTrxRequest(request, {'Eth': [user, sig]}, nonce)
191+
// console.log("🏁", await tx.send())
192+
// })
193+
// });
194+
195+
// r.defineCommand('eth_network', {
196+
// help: 'Show given Ethereum network',
197+
// action: defineAction(r, async () => {
198+
// console.log(`Network: ${network}`);
199+
// })
200+
// });
201+
202+
// r.defineCommand('eth_from', {
203+
// help: 'Show default from Ethereum address',
204+
// action: defineAction(r, async () => {
205+
// console.log(`From: ${saddle.network_config.default_account}`);
206+
// })
207+
// });
208+
209+
// r.defineCommand('eth_deployed', {
210+
// help: 'Show given deployed Ethereum contracts',
211+
// action: defineAction(r, async () => {
212+
// Object.entries(contracts).forEach(([contract, deployed]) => {
213+
// console.log(`${contract}: ${deployed || ""}`);
214+
// });
215+
// })
216+
// });
217+
// }
218+
219+
// function defineContracts(r, saddle, contractInsts) {
220+
// Object.entries(contractInsts).forEach(([contract, contractInst]) => {
221+
// Object.defineProperty(r.context, lowerCase(contract), {
222+
// configurable: true,
223+
// enumerable: true,
224+
// value: contractInst
225+
// });
226+
// });
227+
// }
228+
229+
async function loadChainConfig(chain) {
230+
return JSON.parse(await fs.readFile(path.join(__dirname, chain, 'chain-config.json'), 'utf8'));
231+
}
232+
233+
async function loadTypes(version) {
234+
let releaseTypesFile = path.join(__dirname, '..', 'releases', `m${Number(version)}`, 'types.json');
235+
let baseTypesFile = path.join(__dirname, '..', 'types.json');
236+
if (await fileExists(releaseTypesFile)) {
237+
return JSON.parse(await fs.readFile(releaseTypesFile, 'utf8'));
238+
} else {
239+
console.warn(chalk.yellow(`Cannot find release m${version} types file at ${releaseTypesFile}, using base types.json. Please pull release m${version} with \`scripts/pull_release.sh m${version}\``));
240+
return JSON.parse(await fs.readFile(baseTypesFile, 'utf8'));
241+
}
242+
}
243+
244+
async function rpc(chain, chainConfig, section, method, params=[]) {
245+
if (!chainConfig.rpc) {
246+
throw new Error(`No websocket config for chain ${chain}`);
247+
}
248+
249+
let res = await fetch(chainConfig.rpc, {
250+
method: 'post',
251+
body: JSON.stringify({
252+
jsonrpc: "2.0",
253+
id: 1,
254+
method: `${section}_${method}`,
255+
params
256+
}),
257+
headers: { 'Content-Type': 'application/json' },
258+
});
259+
260+
let resJson = await res.json();
261+
262+
return resJson.result;
263+
}
264+
265+
async function getRuntimeVersion(chain, chainConfig) {
266+
return await rpc(chain, chainConfig, "state", "getRuntimeVersion");
267+
}
268+
269+
function toSS58(keyring, arr) {
270+
return keyring.encodeAddress(arr);
271+
}
272+
273+
function defineKeys(r, obj) {
274+
Object.entries(obj).forEach(([key, value]) => {
275+
Object.defineProperty(r.context, key, {
276+
configurable: false,
277+
enumerable: typeof(value) !== 'function',
278+
value
279+
});
280+
});
281+
}
282+
283+
async function loadScenario(scenarioJson) {
284+
let scenInfo;
285+
try {
286+
scenInfo = JSON.parse(scenarioJson);
287+
} catch (e) {
288+
console.log(`Invalid scenario JSON: ${scenarioJson}`);
289+
throw e;
290+
}
291+
292+
scenInfo.profile = 'release';
293+
scenInfo.log_file = path.join(__dirname, 'repl.log');
294+
295+
let ctx = await buildCtx(scenInfo);
296+
let ethChain = ctx.chains.find('eth');
297+
298+
return ctx;
299+
}
300+
301+
async function startConsole(input, chain, options, scenario) {
302+
let {
303+
verbose,
304+
websocket
305+
} = options;
306+
307+
let ctx = await loadScenario(scenario);
308+
309+
console.info(`Gateway console on chain ${chain}`);
310+
311+
// Object.entries(contracts).forEach(([contract, deployed]) => {
312+
// if (deployed) {
313+
// console.log(`\t${lowerCase(contract)}: ${deployed}`);
314+
// }
315+
// });
316+
317+
let r = repl.start({
318+
prompt: '> ',
319+
input: input,
320+
output: input ? process.stdout : undefined,
321+
terminal: input ? false : undefined
322+
});
323+
if (typeof(r.setupHistory) === 'function') {
324+
r.setupHistory(path.join(process.cwd(), '.repl_history'), (err, repl) => null);
325+
}
326+
r.originalCompleter = r.completer;
327+
r.completer = getCompletions(r.completer, {});
328+
329+
// defineCommands(r, saddle, network, contracts);
330+
331+
defineKeys(r, ctx);
332+
//defineKeys(r, { saddle });
333+
//defineContracts(r, saddle, contractInsts);
334+
335+
process.on('uncaughtException', () => console.log('Error'));
336+
337+
r.on('exit', () => {
338+
process.exit();
339+
});
340+
}
341+
342+
let input;
343+
const options = getopts(process.argv.slice(2), {
344+
alias: {
345+
script: "s",
346+
eval: "e",
347+
chain: "c",
348+
websocket: "w",
349+
verbose: "v",
350+
scenario: "x",
351+
info_file: "i"
352+
},
353+
});
354+
355+
let chain;
356+
let scenario;
357+
if (options.info_file) {
358+
scenario = readFileSync(options.info_file);
359+
chain = 'scenario';
360+
}
361+
if (options.scenario) {
362+
scenario = options.scenario;
363+
chain = 'scenario';
364+
} else {
365+
chain = options.chain || 'testnet';
366+
}
367+
368+
if (options.script) {
369+
input = createReadStream(options.script);
370+
} else if (options.eval) {
371+
let evalArg = options.eval;
372+
let codes = Array.isArray(evalArg) ? evalArg.map((e) => e + ';\n') : [ evalArg ];
373+
input = Readable.from(codes);
374+
}
375+
376+
startConsole(input, chain, options, scenario);

0 commit comments

Comments
 (0)