111 lines
3.6 KiB
JavaScript
111 lines
3.6 KiB
JavaScript
'use strict'
|
|
|
|
const ethUtil = require('ethereumjs-util');
|
|
const fs = require('fs');
|
|
const _ = require('lodash');
|
|
const path = require('path');
|
|
const process = require('process');
|
|
|
|
const keccak256 = ethUtil.keccak256 || ethUtil.sha3;
|
|
const ARGS = process.argv.slice(2);
|
|
const INDENT = ' ';
|
|
const LINEBREAK = '\n';
|
|
const VISIBILITY = 'internal';
|
|
|
|
(function () {
|
|
const [ exchangeArtifactsFile, outputFile ] = ARGS;
|
|
const exchangeArtifacts = require(exchangeArtifactsFile);
|
|
const contractName = path.basename(outputFile, '.sol');
|
|
const functionsByName = extractFunctions(
|
|
exchangeArtifacts.compilerOutput.abi,
|
|
);
|
|
const contractDefinition = defineContract(contractName, functionsByName);
|
|
const preamble = extractOutputFilePreamble(outputFile);
|
|
const outputFileContents = `${preamble}${contractDefinition}`;
|
|
fs.writeFileSync(outputFile, outputFileContents);
|
|
console.log(`Wrote exchange selectors to "${path.resolve(outputFile)}."`);
|
|
})();
|
|
|
|
function extractFunctions(abi) {
|
|
const selectorsByName = {};
|
|
for (const method of abi) {
|
|
if (method.type !== 'function') {
|
|
continue;
|
|
}
|
|
const name = method.name;
|
|
const signature = `${name}(${encodeMethodInputs(method.inputs)})`;
|
|
const selector = `0x${keccak256(signature).slice(0, 4).toString('hex')}`;
|
|
if (!selectorsByName[name]) {
|
|
selectorsByName[name] = [];
|
|
}
|
|
selectorsByName[name].push({
|
|
selector,
|
|
signature,
|
|
});
|
|
}
|
|
return selectorsByName;
|
|
}
|
|
|
|
function defineContract(contractName, functionsByName) {
|
|
const constantDefinitions = [];
|
|
const sortedFunctionNames = _.sortBy(_.keys(functionsByName), name => name.toLowerCase());
|
|
for (const name of sortedFunctionNames) {
|
|
const fns = functionsByName[name];
|
|
for (let idx = 0; idx < fns.length; idx++) {
|
|
const constantLines = generateSelectorConstantDefinition(
|
|
name,
|
|
fns[idx],
|
|
idx,
|
|
fns.length,
|
|
);
|
|
constantDefinitions.push(constantLines);
|
|
}
|
|
}
|
|
return [
|
|
`contract ${contractName} {`,
|
|
`${INDENT}// solhint-disable max-line-length`,
|
|
'',
|
|
constantDefinitions
|
|
.map(lines => lines.map(line => `${INDENT}${line}`))
|
|
.map(lines => lines.join(LINEBREAK))
|
|
.join(`${LINEBREAK}${LINEBREAK}`),
|
|
`}`,
|
|
].join(LINEBREAK);
|
|
}
|
|
|
|
function extractOutputFilePreamble(outputFile) {
|
|
const preambleLines = [];
|
|
const outputFileLines = fs.readFileSync(outputFile, 'utf-8').split(/\r?\n/);
|
|
for (const line of outputFileLines) {
|
|
if (/^\s*contract\s+[a-zA-Z][a-zA-Z0-9_]+/.test(line)) {
|
|
preambleLines.push('');
|
|
break;
|
|
}
|
|
preambleLines.push(line);
|
|
}
|
|
return preambleLines.join(LINEBREAK);
|
|
}
|
|
|
|
function generateSelectorConstantDefinition(name, selector, idx, total) {
|
|
const varName =
|
|
_.snakeCase(total == 1 ? name : `${name}_${idx+1}`).toUpperCase();
|
|
return [
|
|
`// ${selector.signature}`,
|
|
`bytes4 constant ${VISIBILITY} ${varName}_SELECTOR = ${selector.selector};`,
|
|
];
|
|
}
|
|
|
|
function encodeMethodInputs(inputs) {
|
|
const types = [];
|
|
for (const input of inputs) {
|
|
if (input.type === 'tuple') {
|
|
types.push(`(${encodeMethodInputs(input.components)})`);
|
|
} else if (input.type === 'tuple[]') {
|
|
types.push(`(${encodeMethodInputs(input.components)})[]`);
|
|
} else {
|
|
types.push(input.type);
|
|
}
|
|
}
|
|
return types.join(',');
|
|
}
|