resolc crate (#328)

- Factor the YUL crate out of `revive-solidity`.
- `revive-solidity` is in reality not a Solidity implementation but the
revive solidity compiler driver (`resolc`). By renaming we not only get
this straight but also a binary with the same name as the crate which
should be less confusing.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-05-27 09:48:43 +02:00
committed by GitHub
parent 090e3ac13c
commit bd4e108bb0
99 changed files with 599 additions and 624 deletions
@@ -0,0 +1,4 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
};
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="27" failures="0" errors="0" time="2.146">
<testsuite name="Run with --yul by default" errors="0" failures="0" skipped="1" timestamp="2024-10-24T17:08:50" time="1.508" tests="6">
<testcase classname="Run with --yul by default Valid command exit code = 0" name="Run with --yul by default Valid command exit code = 0" time="0.003">
</testcase>
<testcase classname="Run with --yul by default --yul output is presented" name="Run with --yul by default --yul output is presented" time="0">
</testcase>
<testcase classname="Run with --yul by default solc exit code == resolc exit code" name="Run with --yul by default solc exit code == resolc exit code" time="0">
<skipped/>
</testcase>
<testcase classname="Run with --yul by default run invalid: resolc --yul" name="Run with --yul by default run invalid: resolc --yul" time="0.001">
</testcase>
<testcase classname="Run with --yul by default Invalid command exit code = 1" name="Run with --yul by default Invalid command exit code = 1" time="0">
</testcase>
<testcase classname="Run with --yul by default Invalid solc exit code == Invalid resolc exit code" name="Run with --yul by default Invalid solc exit code == Invalid resolc exit code" time="0.041">
</testcase>
</testsuite>
<testsuite name="Run with --asm by default" errors="0" failures="0" skipped="0" timestamp="2024-10-24T17:08:50" time="1.512" tests="6">
<testcase classname="Run with --asm by default Valid command exit code = 0" name="Run with --asm by default Valid command exit code = 0" time="0.002">
</testcase>
<testcase classname="Run with --asm by default --asm output is presented" name="Run with --asm by default --asm output is presented" time="0.001">
</testcase>
<testcase classname="Run with --asm by default solc exit code == resolc exit code" name="Run with --asm by default solc exit code == resolc exit code" time="0.044">
</testcase>
<testcase classname="Run with --asm by default run invalid: resolc --asm" name="Run with --asm by default run invalid: resolc --asm" time="0">
</testcase>
<testcase classname="Run with --asm by default Invalid command exit code = 1" name="Run with --asm by default Invalid command exit code = 1" time="0.001">
</testcase>
<testcase classname="Run with --asm by default Invalid solc exit code == Invalid resolc exit code" name="Run with --asm by default Invalid solc exit code == Invalid resolc exit code" time="0.04">
</testcase>
</testsuite>
<testsuite name="Run resolc without any options" errors="0" failures="0" skipped="2" timestamp="2024-10-24T17:08:50" time="2.016" tests="15">
<testcase classname="Run resolc without any options Info with help is presented" name="Run resolc without any options Info with help is presented" time="0.002">
</testcase>
<testcase classname="Run resolc without any options Exit code = 1" name="Run resolc without any options Exit code = 1" time="0">
</testcase>
<testcase classname="Run resolc without any options solc exit code == resolc exit code" name="Run resolc without any options solc exit code == resolc exit code" time="0.044">
</testcase>
<testcase classname="Default run a command from the help Compiler run successful" name="Default run a command from the help Compiler run successful" time="0">
</testcase>
<testcase classname="Default run a command from the help Exit code = 0" name="Default run a command from the help Exit code = 0" time="0.001">
</testcase>
<testcase classname="Default run a command from the help Output dir is created" name="Default run a command from the help Output dir is created" time="0">
</testcase>
<testcase classname="Default run a command from the help Output file is created" name="Default run a command from the help Output file is created" time="0">
<skipped/>
</testcase>
<testcase classname="Default run a command from the help the output file is not empty" name="Default run a command from the help the output file is not empty" time="0">
</testcase>
<testcase classname="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" name="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" time="0">
</testcase>
<testcase classname="Default run a command from the help Compiler run successful" name="Default run a command from the help Compiler run successful" time="0.001">
</testcase>
<testcase classname="Default run a command from the help Exit code = 0" name="Default run a command from the help Exit code = 0" time="0">
</testcase>
<testcase classname="Default run a command from the help Output dir is created" name="Default run a command from the help Output dir is created" time="0">
</testcase>
<testcase classname="Default run a command from the help Output files are created" name="Default run a command from the help Output files are created" time="0">
<skipped/>
</testcase>
<testcase classname="Default run a command from the help the output files are not empty" name="Default run a command from the help the output files are not empty" time="0.003">
</testcase>
<testcase classname="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" name="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" time="0">
</testcase>
</testsuite>
</testsuites>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
{
"name": "cli-tests",
"version": "1.0.0",
"title": "resolc CLI Tests",
"description": "Auto tests for verifying resolc CLI",
"repository": "https://github.com/paritytech/revive",
"main": "index.js",
"private": true,
"scripts": {
"test": "npx jest --verbose --testPathPattern="
},
"keywords": [],
"author": "Matter Labs",
"contributors": [
"cyrill@parity.io"
],
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/shelljs": "^0.8.15",
"jest": "^29.7.0",
"shelljs": "^0.8.5",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3"
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C {}
@@ -0,0 +1,54 @@
object "Test" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("Test_deployed")
codecopy(0, dataoffset("Test_deployed"), _1)
return(0, _1)
}
}
object "Test_deployed" {
code {
{
mstore(64, 128)
if iszero(lt(calldatasize(), 4))
{
let _1 := 0
switch shr(224, calldataload(_1))
case 0x3df4ddf4 {
if callvalue() { revert(_1, _1) }
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
let memPos := allocate_memory(_1)
mstore(memPos, 0x2a)
return(memPos, 32)
}
case 0x5a8ac02d {
if callvalue() { revert(_1, _1) }
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
let memPos_1 := allocate_memory(_1)
return(memPos_1, sub(abi_encode_uint256(memPos_1, 0x63), memPos_1))
}
}
revert(0, 0)
}
function abi_encode_uint256(headStart, value0) -> tail
{
tail := add(headStart, 32)
mstore(headStart, value0)
}
function allocate_memory(size) -> memPtr
{
memPtr := mload(64)
let newFreePtr := add(memPtr, and(add(size, 31), not(31)))
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))
{
mstore(0, shl(224, 0x4e487b71))
mstore(4, 0x41)
revert(0, 0x24)
}
mstore(64, newFreePtr)
}
}
}
}
@@ -0,0 +1,47 @@
import * as path from 'path'
const outputDir = 'artifacts'
const binExtension = ':C.pvm'
const asmExtension = ':C.pvmasm'
const llvmExtension = '.ll'
const contractSolFilename = 'contract.sol'
const contractYulFilename = 'contract.yul'
const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized'
const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized'
const pathToOutputDir = path.join(__dirname, '..', outputDir)
const pathToContracts = path.join(__dirname, '..', 'src', 'contracts')
const pathToBasicYulContract = path.join(
pathToContracts,
'yul',
contractYulFilename
)
const pathToBasicSolContract = path.join(
pathToContracts,
'solidity',
contractSolFilename
)
const pathToSolBinOutputFile = path.join(
pathToOutputDir,
contractSolFilename + binExtension
)
const pathToSolAsmOutputFile = path.join(
pathToOutputDir,
contractSolFilename + asmExtension
)
export const paths = {
outputDir: outputDir,
binExtension: binExtension,
asmExtension: asmExtension,
llvmExtension: llvmExtension,
contractSolFilename: contractSolFilename,
contractYulFilename: contractYulFilename,
contractOptimizedLLVMFilename: contractOptimizedLLVMFilename,
contractUnoptimizedLLVMFilename: contractUnoptimizedLLVMFilename,
pathToOutputDir: pathToOutputDir,
pathToContracts: pathToContracts,
pathToBasicSolContract: pathToBasicSolContract,
pathToBasicYulContract: pathToBasicYulContract,
pathToSolBinOutputFile: pathToSolBinOutputFile,
pathToSolAsmOutputFile: pathToSolAsmOutputFile,
}
@@ -0,0 +1,51 @@
import * as shell from 'shelljs'
import * as fs from 'fs'
import { spawnSync } from 'child_process'
interface CommandResult {
output: string
exitCode: number
}
export const executeCommand = (
command: string,
stdin?: string
): CommandResult => {
if (stdin) {
const process = spawnSync(command, [], {
input: stdin,
shell: true,
encoding: 'utf8',
maxBuffer: 30 * 1024 * 1024,
})
return {
exitCode: process.status || 0,
output: (process.stdout || process.stderr || '').toString(),
}
}
const result = shell.exec(command, { silent: true, async: false })
return {
exitCode: result.code,
output: result.stdout || result.stderr || '',
}
}
export const isFolderExist = (folder: string): boolean => {
return shell.test('-d', folder)
}
export const isFileExist = (
pathToFileDir: string,
fileName: string,
fileExtension: string
): boolean => {
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension)
}
export const isFileEmpty = (file: string): boolean => {
if (fs.existsSync(file)) {
return fs.readFileSync(file).length === 0
}
}
@@ -0,0 +1,44 @@
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
//id1746
describe('Run with --asm by default', () => {
const command = `resolc ${paths.pathToBasicSolContract} --asm`
const result = executeCommand(command)
const commandInvalid = 'resolc --asm'
const resultInvalid = executeCommand(commandInvalid)
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('--asm output is presented', () => {
const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i]
for (const pattern of expectedPatterns) {
expect(result.output).toMatch(pattern)
}
})
it('solc exit code == resolc exit code', () => {
const command = `solc ${paths.pathToBasicSolContract} --asm`
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
it('run invalid: resolc --asm', () => {
expect(resultInvalid.output).toMatch(
/(No input sources specified|Compilation aborted)/i
)
})
it('Invalid command exit code = 1', () => {
expect(resultInvalid.exitCode).toBe(1)
})
it('Invalid solc exit code == Invalid resolc exit code', () => {
const command = 'solc --asm'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(resultInvalid.exitCode)
})
})
@@ -0,0 +1,241 @@
import {
executeCommand,
isFolderExist,
isFileExist,
isFileEmpty,
} from '../src/helper'
import { paths } from '../src/entities'
import * as shell from 'shelljs'
import * as path from 'path'
//id1762
describe('Run resolc without any options', () => {
const command = 'resolc'
const result = executeCommand(command)
it('Info with help is presented', () => {
expect(result.output).toMatch(/(Usage: resolc)/i)
})
it('Exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('solc exit code == resolc exit code', () => {
const command = 'solc'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//#1713
describe('Default run a command from the help', () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"` // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command)
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
xit('Output file is created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension
)
).toBe(true)
})
it('the output file is not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
})
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i)
})
})
//#1818
describe('Default run a command from the help', () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"` // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command)
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
xit('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i)
})
})
describe('Run resolc with source debug information', () => {
const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
] // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
const command = commands[idx]
const result = executeCommand(command)
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
it('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i)
})
}
})
describe('Run resolc with source debug information, check LLVM debug-info', () => {
const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
] // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
const command = commands[idx]
const result = executeCommand(command)
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
it('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractOptimizedLLVMFilename,
paths.llvmExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractUnoptimizedLLVMFilename,
paths.llvmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i)
})
}
})
describe('Standard JSON compilation with path options', () => {
const contractsDir = path.join(shell.tempdir(), 'contracts-test')
const inputFile = path.join(__dirname, '..', 'src/contracts/compiled/1.json')
beforeAll(() => {
shell.mkdir('-p', contractsDir)
const input = JSON.parse(shell.cat(inputFile).toString())
Object.entries(input.sources).forEach(
([sourcePath, source]: [string, any]) => {
const filePath = path.join(contractsDir, sourcePath)
shell.mkdir('-p', path.dirname(filePath))
shell.ShellString(source.content).to(filePath)
}
)
})
afterAll(() => {
shell.rm('-rf', contractsDir)
})
describe('Output with all path options', () => {
let result: { exitCode: number; output: string }
beforeAll(() => {
const tempInputFile = path.join(contractsDir, 'temp-input.json')
shell.cp(inputFile, tempInputFile)
const inputContent = shell.cat(inputFile).toString()
const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`
result = executeCommand(command, inputContent)
shell.rm(tempInputFile)
})
it('Compiler run successful without emiting warnings', () => {
const parsedResults = JSON.parse(result.output)
expect(
parsedResults.errors.filter(
(error: { type: string }) => error.type != 'Warning'
)
).toEqual([])
})
})
})
@@ -0,0 +1,39 @@
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
//id1743
describe('Run with --yul by default', () => {
const command = `resolc ${paths.pathToBasicYulContract} --yul`
const result = executeCommand(command)
const commandInvalid = 'resolc --yul'
const resultInvalid = executeCommand(commandInvalid)
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('--yul output is presented', () => {
expect(result.output).toMatch(/(Compiler run successful)/i)
expect(result.output).toMatch(/(No output requested)/i)
})
xit('solc exit code == resolc exit code', () => {
// unknown solc issue for datatype of the contract
const command = `solc ${paths.pathToBasicSolContract} --yul`
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
it('run invalid: resolc --yul', () => {
expect(resultInvalid.output).toMatch(/(The input file is missing)/i)
})
it('Invalid command exit code = 1', () => {
expect(resultInvalid.exitCode).toBe(1)
})
it('Invalid solc exit code == Invalid resolc exit code', () => {
const command = 'solc --yul'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(resultInvalid.exitCode)
})
})
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"outDir": "./dist",
}
}
@@ -0,0 +1,188 @@
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
describe('Set of --combined-json tests', () => {
const zksolcCommand = 'zksolc'
const solcCommand = 'solc'
const json_args: string[] = [
`abi`,
`hashes`,
`metadata`,
`devdoc`,
`userdoc`,
`storage-layout`,
`ast`,
`asm`,
`bin`,
`bin-runtime`,
]
//id1742:I
describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const args = [`--combined-json`]
const result = executeCommand(zksolcCommand, args)
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i)
})
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//id1742:II
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`]
const result = executeCommand(zksolcCommand, args)
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i)
})
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//id1742:III
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(contracts)/i)
})
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1829:I
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`--${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(Invalid option|error)/i)
})
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1829:II
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
xit('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(
/(No such file or directory|cannot find the file specified)/i
) // Hopefully we should have more precise message here!
})
xit('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1829:III
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
`--combined-json`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i)
})
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1830
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicYulContract}`,
`--combined-json`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i)
})
asd
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
})
@@ -0,0 +1,88 @@
//! The Solidity compiler unit tests for factory dependencies.
#![cfg(test)]
use std::collections::BTreeMap;
pub const MAIN_CODE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.16;
import "./callable.sol";
contract Main {
function main() external returns(uint256) {
Callable callable = new Callable();
callable.set(10);
return callable.get();
}
}
"#;
pub const CALLABLE_CODE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.16;
contract Callable {
uint256 value;
function set(uint256 x) external {
value = x;
}
function get() external view returns(uint256) {
return value;
}
}
"#;
#[test]
fn default() {
let mut sources = BTreeMap::new();
sources.insert("main.sol".to_owned(), MAIN_CODE.to_owned());
sources.insert("callable.sol".to_owned(), CALLABLE_CODE.to_owned());
let output = super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Build failure");
assert_eq!(
output
.contracts
.as_ref()
.expect("Missing field `contracts`")
.get("main.sol")
.expect("Missing file `main.sol`")
.get("Main")
.expect("Missing contract `main.sol:Main`")
.factory_dependencies
.as_ref()
.expect("Missing field `factory_dependencies`")
.len(),
1,
"Expected 1 factory dependency in `main.sol:Main`"
);
assert_eq!(
output
.contracts
.as_ref()
.expect("Missing field `contracts`")
.get("callable.sol")
.expect("Missing file `callable.sol`")
.get("Callable")
.expect("Missing contract `callable.sol:Callable`")
.factory_dependencies
.as_ref()
.expect("Missing field `factory_dependencies`")
.len(),
0,
"Expected 0 factory dependencies in `callable.sol:Callable`"
);
}
+45
View File
@@ -0,0 +1,45 @@
//! The Solidity compiler unit tests for IR artifacts.
//! The tests check if the IR artifacts are kept in the final output.
#![cfg(test)]
use std::collections::BTreeMap;
#[test]
fn yul() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test {
function main() public view returns (uint) {
return 42;
}
}
"#;
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), source_code.to_owned());
let build = super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.ir_optimized
.is_some(),
"Yul IR is missing"
);
}
+90
View File
@@ -0,0 +1,90 @@
//! The Solidity compiler unit tests for libraries.
#![cfg(test)]
use std::collections::BTreeMap;
pub const LIBRARY_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// A simple library with at least one external method
library SimpleLibrary {
function add(uint256 a, uint256 b) external pure returns (uint256) {
return a + b;
}
}
// A contract calling that library
contract SimpleContract {
using SimpleLibrary for uint256;
function performAlgorithm(uint256 a, uint256 b) public pure returns (uint256) {
uint sum = 0;
if (a > b) {
while (true) {
sum += a.add(b);
}
}
return sum;
}
}
"#;
#[test]
fn not_specified() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned());
let output =
super::build_solidity_and_detect_missing_libraries(sources.clone(), BTreeMap::new())
.expect("Test failure");
assert!(
output
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("SimpleContract")
.expect("Always exists")
.missing_libraries
.as_ref()
.expect("Always exists")
.contains("test.sol:SimpleLibrary"),
"Missing library not detected"
);
}
#[test]
fn specified() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned());
let mut libraries = BTreeMap::new();
libraries
.entry("test.sol".to_string())
.or_insert_with(BTreeMap::new)
.entry("SimpleLibrary".to_string())
.or_insert("0x00000000000000000000000000000000DEADBEEF".to_string());
let output =
super::build_solidity_and_detect_missing_libraries(sources.clone(), libraries.clone())
.expect("Test failure");
assert!(
output
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("SimpleContract")
.expect("Always exists")
.missing_libraries
.as_ref()
.cloned()
.unwrap_or_default()
.is_empty(),
"The list of missing libraries must be empty"
);
}
+251
View File
@@ -0,0 +1,251 @@
//! The Solidity compiler unit tests for messages.
#![cfg(test)]
use std::collections::BTreeMap;
use revive_solc_json_interface::warning::Warning;
pub const ECRECOVER_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ECRecoverExample {
function recoverAddress(
bytes32 messageHash,
uint8 v,
bytes32 r,
bytes32 s
) public pure returns (address) {
return ecrecover(messageHash, v, r, s);
}
}
"#;
#[test]
fn ecrecover() {
assert!(
super::check_solidity_warning(
ECRECOVER_TEST_SOURCE,
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
BTreeMap::new(),
false,
None,
).expect("Test failure")
);
}
#[test]
fn ecrecover_suppressed() {
assert!(
!super::check_solidity_warning(
ECRECOVER_TEST_SOURCE,
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
BTreeMap::new(),
false,
Some(vec![Warning::EcRecover]),
).expect("Test failure")
);
}
pub const SEND_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SendExample {
address payable public recipient;
constructor(address payable _recipient) {
recipient = _recipient;
}
function forwardEther() external payable {
bool success = recipient.send(msg.value);
require(success, "Failed to send Ether");
}
}
"#;
pub const BALANCE_CALLS_MESSAGE: &str =
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)'";
#[test]
fn send() {
assert!(super::check_solidity_warning(
SEND_TEST_SOURCE,
BALANCE_CALLS_MESSAGE,
BTreeMap::new(),
false,
None,
)
.expect("Test failure"));
}
#[test]
fn send_suppressed() {
assert!(!super::check_solidity_warning(
SEND_TEST_SOURCE,
BALANCE_CALLS_MESSAGE,
BTreeMap::new(),
false,
Some(vec![Warning::SendTransfer]),
)
.expect("Test failure"));
}
pub const TRANSFER_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TransferExample {
address payable public recipient;
constructor(address payable _recipient) {
recipient = _recipient;
}
function forwardEther() external payable {
recipient.transfer(msg.value);
}
}
"#;
#[test]
fn transfer() {
assert!(super::check_solidity_warning(
TRANSFER_TEST_SOURCE,
BALANCE_CALLS_MESSAGE,
BTreeMap::new(),
false,
None,
)
.expect("Test failure"));
}
#[test]
fn transfer_suppressed() {
assert!(!super::check_solidity_warning(
TRANSFER_TEST_SOURCE,
BALANCE_CALLS_MESSAGE,
BTreeMap::new(),
false,
Some(vec![Warning::SendTransfer]),
)
.expect("Test failure"));
}
pub const EXTCODESIZE_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ExternalCodeSize {
function getExternalCodeSize(address target) public view returns (uint256) {
uint256 codeSize;
assembly {
codeSize := extcodesize(target)
}
return codeSize;
}
}
"#;
#[test]
fn extcodesize() {
assert!(super::check_solidity_warning(
EXTCODESIZE_TEST_SOURCE,
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
BTreeMap::new(),
false,
None,
)
.expect("Test failure"));
}
#[test]
fn extcodesize_suppressed() {
assert!(!super::check_solidity_warning(
EXTCODESIZE_TEST_SOURCE,
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
BTreeMap::new(),
false,
Some(vec![Warning::ExtCodeSize]),
)
.expect("Test failure"));
}
pub const TX_ORIGIN_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TxOriginExample {
function isOriginSender() public view returns (bool) {
return tx.origin == msg.sender;
}
}
"#;
#[test]
fn tx_origin() {
assert!(super::check_solidity_warning(
TX_ORIGIN_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(),
false,
None,
)
.expect("Test failure"));
}
#[test]
fn tx_origin_suppressed() {
assert!(!super::check_solidity_warning(
TX_ORIGIN_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(),
false,
Some(vec![Warning::TxOrigin]),
)
.expect("Test failure"));
}
pub const TX_ORIGIN_ASSEMBLY_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TxOriginExample {
function isOriginSender() public view returns (bool) {
address txOrigin;
address sender = msg.sender;
assembly {
txOrigin := origin() // Get the transaction origin using the 'origin' instruction
}
return txOrigin == sender;
}
}
"#;
#[test]
fn tx_origin_assembly() {
assert!(super::check_solidity_warning(
TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(),
false,
None,
)
.expect("Test failure"));
}
#[test]
fn tx_origin_assembly_suppressed() {
assert!(!super::check_solidity_warning(
TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(),
false,
Some(vec![Warning::TxOrigin]),
)
.expect("Test failure"));
}
+14
View File
@@ -0,0 +1,14 @@
//! The Solidity compiler unit tests.
#![cfg(test)]
mod factory_dependency;
mod ir_artifacts;
mod libraries;
mod messages;
mod optimizer;
mod remappings;
mod runtime_code;
mod unsupported_opcodes;
pub(crate) use super::test_utils::*;
+130
View File
@@ -0,0 +1,130 @@
//! The Solidity compiler unit tests for the optimizer.
#![cfg(test)]
use std::collections::BTreeMap;
pub const SOURCE_CODE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
contract Test {
uint8 constant ARRAY_SIZE = 40;
uint128 constant P = 257;
uint128 constant MODULO = 1000000007;
function complex() public pure returns(uint64) {
uint8[ARRAY_SIZE] memory array;
// generate array where first half equals second
for(uint8 i = 0; i < ARRAY_SIZE; i++) {
array[i] = (i % (ARRAY_SIZE / 2)) * (255 / (ARRAY_SIZE / 2 - 1));
}
bool result = true;
for(uint8 i = 0; i < ARRAY_SIZE/2; i++) {
result = result && hash(array, 0, i + 1) == hash(array, ARRAY_SIZE/2, ARRAY_SIZE/2 + i + 1)
&& hash(array, i, ARRAY_SIZE/2) == hash(array, i + ARRAY_SIZE/2, ARRAY_SIZE);
}
if (result) {
return 1;
} else {
return 0;
}
}
function hash(uint8[ARRAY_SIZE] memory array, uint8 begin, uint8 end) private pure returns(uint128) {
uint128 h = 0;
for(uint8 i = begin; i < end; i++) {
h = (h * P + array[i]) % MODULO;
}
return h;
}
}
"#;
#[test]
fn optimizer() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), SOURCE_CODE.to_owned());
let build_unoptimized = super::build_solidity(
sources.clone(),
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::none(),
)
.expect("Build failure");
let build_optimized_for_cycles = super::build_solidity(
sources.clone(),
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Build failure");
let build_optimized_for_size = super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::size(),
)
.expect("Build failure");
let size_when_unoptimized = build_unoptimized
.contracts
.as_ref()
.expect("Missing field `contracts`")
.get("test.sol")
.expect("Missing file `test.sol`")
.get("Test")
.expect("Missing contract `test.sol:Test`")
.evm
.as_ref()
.expect("Missing EVM data")
.bytecode
.as_ref()
.expect("Missing bytecode")
.object
.len();
let size_when_optimized_for_cycles = build_optimized_for_cycles
.contracts
.as_ref()
.expect("Missing field `contracts`")
.get("test.sol")
.expect("Missing file `test.sol`")
.get("Test")
.expect("Missing contract `test.sol:Test`")
.evm
.as_ref()
.expect("Missing EVM data")
.bytecode
.as_ref()
.expect("Missing bytecode")
.object
.len();
let size_when_optimized_for_size = build_optimized_for_size
.contracts
.as_ref()
.expect("Missing field `contracts`")
.get("test.sol")
.expect("Missing file `test.sol`")
.get("Test")
.expect("Missing contract `test.sol:Test`")
.evm
.as_ref()
.expect("Missing EVM data")
.bytecode
.as_ref()
.expect("Missing bytecode")
.object
.len();
assert!(
size_when_optimized_for_cycles < size_when_unoptimized,
"Expected the cycles-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_cycles, size_when_unoptimized,
);
assert!(
size_when_optimized_for_size < size_when_unoptimized,
"Expected the size-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_size, size_when_unoptimized,
);
}
+50
View File
@@ -0,0 +1,50 @@
//! The Solidity compiler unit tests for remappings.
#![cfg(test)]
use std::collections::BTreeMap;
use std::collections::BTreeSet;
pub const CALLEE_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.16;
contract Callable {
function f(uint a) public pure returns(uint) {
return a * 2;
}
}
"#;
pub const CALLER_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.16;
import "libraries/default/callable.sol";
contract Main {
function main(Callable callable) public returns(uint) {
return callable.f(5);
}
}
"#;
#[test]
fn default() {
let mut sources = BTreeMap::new();
sources.insert("./test.sol".to_owned(), CALLER_TEST_SOURCE.to_owned());
sources.insert("./callable.sol".to_owned(), CALLEE_TEST_SOURCE.to_owned());
let mut remappings = BTreeSet::new();
remappings.insert("libraries/default/=./".to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
Some(remappings),
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
+33
View File
@@ -0,0 +1,33 @@
//! The Solidity compiler unit tests for runtime code.
#![cfg(test)]
use std::collections::BTreeMap;
#[test]
#[should_panic(expected = "runtimeCode is not supported")]
fn default() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract A {}
contract Test {
function main() public pure returns(bytes memory) {
return type(A).runtimeCode;
}
}
"#;
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), source_code.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
@@ -0,0 +1,166 @@
//! The Solidity compiler unit tests for unsupported opcodes.
#![cfg(test)]
use std::collections::BTreeMap;
#[test]
#[should_panic(expected = "The `CODECOPY` instruction is not supported")]
fn codecopy_yul_runtime() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract FixedCodeCopy {
function copyCode() public view returns (bytes memory) {
uint256 fixedCodeSize = 64;
bytes memory code = new bytes(fixedCodeSize);
assembly {
codecopy(add(code, 0x20), 0, fixedCodeSize)
}
return code;
}
}
"#;
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), source_code.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
pub const CALLCODE_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CallcodeTest {
function testCallcode(address target, bytes4 signature, uint256 inputValue) public returns (bool) {
bool success;
assembly {
let input := mload(0x40)
mstore(input, signature)
mstore(add(input, 0x04), inputValue)
let callResult := callcode(gas(), target, 0, input, 0x24, 0, 0)
success := and(callResult, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
return success;
}
}
"#;
#[test]
#[should_panic(expected = "The `CALLCODE` instruction is not supported")]
fn callcode_yul() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), CALLCODE_TEST_SOURCE.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
#[test]
#[should_panic(expected = "The `PC` instruction is not supported")]
fn pc_yul() {
let source_code = r#"
object "ProgramCounter" {
code {
datacopy(0, dataoffset("ProgramCounter_deployed"), datasize("ProgramCounter_deployed"))
return(0, datasize("ProgramCounter_deployed"))
}
object "ProgramCounter_deployed" {
code {
function getPC() -> programCounter {
programCounter := pc()
}
let pcValue := getPC()
sstore(0, pcValue)
}
}
}
"#;
super::build_yul(source_code).expect("Test failure");
}
pub const EXTCODECOPY_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ExternalCodeCopy {
function copyExternalCode(address target, uint256 codeSize) public view returns (bytes memory) {
bytes memory code = new bytes(codeSize);
assembly {
extcodecopy(target, add(code, 0x20), 0, codeSize)
}
return code;
}
}
"#;
#[test]
#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")]
fn extcodecopy_yul() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), EXTCODECOPY_TEST_SOURCE.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
pub const SELFDESTRUCT_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MinimalDestructible {
address payable public owner;
constructor() {
owner = payable(msg.sender);
}
function destroy() public {
require(msg.sender == owner, "Only the owner can call this function.");
selfdestruct(owner);
}
}
"#;
#[test]
#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")]
fn selfdestruct_yul() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), SELFDESTRUCT_TEST_SOURCE.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}