mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-14 22:41:07 +00:00
7656c6a8b4
- Add `RESOLC_BIN` env variable to force use a binary compiler instead of the wasm - Add `--diff-stats` options to print file path e.g ``` ~/github/redstone-oracles-monorepo main* ❯ npx @parity/resolc@latest --diff-stats \ --base-path . \ --include-path node_modules \ --bin packages/evm-connector/contracts/samples/SampleWithEvents.sol ┌─────────┬──────────────────────────────────────────────────────────────────────────┬───────────────────────────────┬────────────┬────────────┬───────────┐ │ (index) │ file │ contract │ polkavm │ bin │ diff │ ├─────────┼──────────────────────────────────────────────────────────────────────────┼───────────────────────────────┼────────────┼────────────┼───────────┤ │ 0 │ 'packages/evm-connector/contracts/core/CalldataExtractor.sol' │ 'CalldataExtractor' │ '4.31 kB' │ '2.53 kB' │ '70.12%' │ │ 1 │ 'packages/evm-connector/contracts/core/RedstoneConstants.sol' │ 'RedstoneConstants' │ '0.80 kB' │ '0.17 kB' │ '368.18%' │ │ 2 │ 'packages/evm-connector/contracts/core/RedstoneDefaultsLib.sol' │ 'RedstoneDefaultsLib' │ '0.90 kB' │ '0.31 kB' │ '189.06%' │ │ 3 │ 'packages/evm-connector/contracts/libs/BitmapLib.sol' │ 'BitmapLib' │ '0.90 kB' │ '0.31 kB' │ '189.06%' │ │ 4 │ 'packages/evm-connector/contracts/libs/NumericArrayLib.sol' │ 'NumericArrayLib' │ '0.90 kB' │ '0.31 kB' │ '189.06%' │ │ 5 │ 'packages/evm-connector/contracts/libs/SignatureLib.sol' │ 'SignatureLib' │ '0.90 kB' │ '0.31 kB' │ '189.06%' │ │ 6 │ 'packages/evm-connector/contracts/mocks/RedstoneConsumerNumericMock.sol' │ 'RedstoneConsumerNumericMock' │ '13.62 kB' │ '10.17 kB' │ '33.96%' │ │ 7 │ 'packages/evm-connector/contracts/samples/SampleWithEvents.sol' │ 'SampleWithEvents' │ '29.34 kB' │ '15.60 kB' │ '88.03%' │ └─────────┴──────────────────────────────────────────────────────────────────────────┴───────────────────────────────┴────────────┴────────────┴───────────┘ ```
246 lines
7.5 KiB
JavaScript
Executable File
246 lines
7.5 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import * as commander from 'commander'
|
|
import * as fs from 'fs'
|
|
import * as os from 'os'
|
|
import * as path from 'path'
|
|
import * as resolc from '.'
|
|
import { SolcInput } from '.'
|
|
import { execSync } from 'child_process'
|
|
|
|
async function main() {
|
|
// hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
|
|
const originalUncaughtExceptionListeners =
|
|
process.listeners('uncaughtException')
|
|
// FIXME: remove annoying exception catcher of Emscripten
|
|
// see https://github.com/chriseth/browser-solidity/issues/167
|
|
process.removeAllListeners('uncaughtException')
|
|
|
|
const program = new commander.Command()
|
|
|
|
program.name('resolcjs')
|
|
program.version(resolc.version())
|
|
program
|
|
.option('--bin', 'Binary of the contracts in hex.')
|
|
.option('--abi', 'ABI of the contracts.')
|
|
.option(
|
|
'--diff-stats',
|
|
'Print statistics about Resolc vs Solc compilation.'
|
|
)
|
|
.option(
|
|
'--base-path <path>',
|
|
'Root of the project source tree. ' +
|
|
'The import callback will attempt to interpret all import paths as relative to this directory.'
|
|
)
|
|
.option(
|
|
'--include-path <path...>',
|
|
'Extra source directories available to the import callback. ' +
|
|
'When using a package manager to install libraries, use this option to specify directories where packages are installed. ' +
|
|
'Can be used multiple times to provide multiple locations.'
|
|
)
|
|
.option(
|
|
'-o, --output-dir <output-directory>',
|
|
'Output directory for the contracts.'
|
|
)
|
|
.option('-p, --pretty-json', 'Pretty-print all JSON output.', false)
|
|
.option('-v, --verbose', 'More detailed console output.', false)
|
|
.argument('<files...>')
|
|
|
|
program.parse(process.argv)
|
|
const options = program.opts<{
|
|
verbose: boolean
|
|
abi: boolean
|
|
bin: boolean
|
|
outputDir?: string
|
|
prettyJson: boolean
|
|
basePath?: string
|
|
diffStats: boolean
|
|
includePath?: string[]
|
|
}>()
|
|
|
|
// when using --stats option, we want to run solc as well to compare outputs size
|
|
if (options.diffStats) {
|
|
const args = process.argv.filter((arg) => !arg.startsWith('--diff-stats'))
|
|
try {
|
|
execSync(`npx --yes solc@latest ${args.slice(2).join(' ')}`)
|
|
} catch (err) {
|
|
abort(`Failed to run solc: ${err}`)
|
|
}
|
|
}
|
|
|
|
const files: string[] = program.args
|
|
const destination = options.outputDir ?? '.'
|
|
|
|
function abort(msg: string) {
|
|
console.error(msg || 'Error occurred')
|
|
process.exit(1)
|
|
}
|
|
|
|
function withUnixPathSeparators(filePath: string) {
|
|
// On UNIX-like systems forward slashes in paths are just a part of the file name.
|
|
if (os.platform() !== 'win32') {
|
|
return filePath
|
|
}
|
|
|
|
return filePath.replace(/\\/g, '/')
|
|
}
|
|
|
|
function makeSourcePathRelativeIfPossible(sourcePath: string) {
|
|
const absoluteBasePath = options.basePath
|
|
? path.resolve(options.basePath)
|
|
: path.resolve('.')
|
|
const absoluteIncludePaths = options.includePath
|
|
? options.includePath.map((prefix: string) => {
|
|
return path.resolve(prefix)
|
|
})
|
|
: []
|
|
|
|
// Compared to base path stripping logic in solc this is much simpler because path.resolve()
|
|
// handles symlinks correctly (does not resolve them except in work dir) and strips .. segments
|
|
// from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it
|
|
// ignores less important corner cases: drive letters are not stripped from absolute paths on
|
|
// Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has
|
|
// very little test coverage so there might be more differences that we are just not aware of.
|
|
const absoluteSourcePath = path.resolve(sourcePath)
|
|
|
|
for (const absolutePrefix of [absoluteBasePath].concat(
|
|
absoluteIncludePaths
|
|
)) {
|
|
const relativeSourcePath = path.relative(
|
|
absolutePrefix,
|
|
absoluteSourcePath
|
|
)
|
|
|
|
if (!relativeSourcePath.startsWith('../')) {
|
|
return withUnixPathSeparators(relativeSourcePath)
|
|
}
|
|
}
|
|
|
|
// File is not located inside base path or include paths so use its absolute path.
|
|
return withUnixPathSeparators(absoluteSourcePath)
|
|
}
|
|
|
|
function toFormattedJson<T>(input: T) {
|
|
return JSON.stringify(input, null, options.prettyJson ? 4 : 0)
|
|
}
|
|
|
|
if (files.length === 0) {
|
|
console.error('Must provide a file')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(options.bin || options.abi)) {
|
|
abort('Invalid option selected, must specify either --bin or --abi')
|
|
}
|
|
|
|
const sources: SolcInput = {}
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
try {
|
|
sources[makeSourcePathRelativeIfPossible(files[i])] = {
|
|
content: fs.readFileSync(files[i]).toString(),
|
|
}
|
|
} catch (e) {
|
|
abort('Error reading ' + files[i] + ': ' + e)
|
|
}
|
|
}
|
|
|
|
if (options.verbose) {
|
|
console.log('>>> Compiling:\n' + toFormattedJson(sources) + '\n')
|
|
}
|
|
|
|
const output = await resolc.compile(sources)
|
|
let hasError = false
|
|
|
|
if (!output) {
|
|
abort('No output from compiler')
|
|
} else if (output.errors && !options.diffStats) {
|
|
for (const error in output.errors) {
|
|
const message = output.errors[error]
|
|
if (message.severity === 'warning') {
|
|
console.log(message.formattedMessage)
|
|
} else {
|
|
console.error(message.formattedMessage)
|
|
hasError = true
|
|
}
|
|
}
|
|
}
|
|
|
|
fs.mkdirSync(destination, { recursive: true })
|
|
|
|
function writeFile(file: string, content: Buffer | string) {
|
|
file = path.join(destination, file)
|
|
fs.writeFile(file, content, function (err) {
|
|
if (err) {
|
|
console.error('Failed to write ' + file + ': ' + err)
|
|
}
|
|
})
|
|
}
|
|
|
|
const contractStats = []
|
|
|
|
for (const fileName in output.contracts) {
|
|
for (const contractName in output.contracts[fileName]) {
|
|
let contractFileName = fileName + ':' + contractName
|
|
contractFileName = contractFileName.replace(/[:./\\]/g, '_')
|
|
let polkavmSize = 0
|
|
let binSize = 0
|
|
if (
|
|
options.bin &&
|
|
output.contracts?.[fileName]?.[contractName]?.evm?.bytecode?.object
|
|
) {
|
|
const pvmData = Buffer.from(
|
|
output.contracts[fileName][contractName].evm.bytecode.object,
|
|
'hex'
|
|
)
|
|
writeFile(contractFileName + '.polkavm', pvmData)
|
|
polkavmSize = pvmData.length
|
|
|
|
const binOutPath = path.join(destination, `${contractFileName}.bin`)
|
|
if (fs.existsSync(binOutPath)) {
|
|
try {
|
|
binSize = fs.statSync(binOutPath).size || 0
|
|
} catch {}
|
|
}
|
|
contractStats.push({
|
|
file: fileName,
|
|
contract: contractName,
|
|
['resolc (kB)']: Number((polkavmSize / 1024).toFixed(2)),
|
|
['solc (kB)']: Number((binSize / 1024).toFixed(2)),
|
|
['diff (%)']:
|
|
binSize > 0
|
|
? Number(((polkavmSize / binSize - 1) * 100).toFixed(2))
|
|
: Number.NaN,
|
|
})
|
|
}
|
|
|
|
if (options.abi) {
|
|
writeFile(
|
|
contractFileName + '.abi',
|
|
toFormattedJson(output.contracts[fileName][contractName].abi)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.diffStats && contractStats.length > 0) {
|
|
console.table(
|
|
contractStats.sort((a, b) => b['resolc (kB)'] - a['resolc (kB)'])
|
|
)
|
|
}
|
|
|
|
// Put back original exception handlers.
|
|
originalUncaughtExceptionListeners.forEach(function (listener) {
|
|
process.addListener('uncaughtException', listener)
|
|
})
|
|
|
|
if (hasError) {
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Error:', err)
|
|
process.exit(1)
|
|
})
|