Compare commits

..

2 Commits

Author SHA1 Message Date
pgherveou 20a3b30296 wip 2025-04-28 16:39:59 +02:00
pgherveou f44b2485bd lint 2025-04-24 16:34:06 +02:00
69 changed files with 5170 additions and 8096 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ rustflags = [
"-Clink-arg=-sWASM_ASYNC_COMPILATION=0", "-Clink-arg=-sWASM_ASYNC_COMPILATION=0",
"-Clink-arg=-sDYNAMIC_EXECUTION=0", "-Clink-arg=-sDYNAMIC_EXECUTION=0",
"-Clink-arg=-sALLOW_TABLE_GROWTH=1", "-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/emscripten/embed/soljson_interface.js", "-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/emscripten/embed/pre.js", "-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=-sSTACK_SIZE=128kb", "-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0" "-Clink-arg=-sNODEJS_CATCH_EXIT=0"
] ]
+18 -19
View File
@@ -1,19 +1,18 @@
name: "Get Emscripten SDK" name: "Get Emscripten SDK"
inputs: inputs:
version: version:
description: "" description: ""
required: false required: false
default: "3.1.64" default: "3.1.64"
runs:
runs: using: "composite"
using: "composite" steps:
steps: - name: install emsdk
- name: install emsdk shell: bash
shell: bash run: |
run: | git clone https://github.com/emscripten-core/emsdk.git ./emsdk/
git clone https://github.com/emscripten-core/emsdk.git ./emsdk/ cd emsdk
cd emsdk git checkout tags/${{ inputs.version }}
git checkout tags/${{ inputs.version }} ./emsdk install ${{ inputs.version }}
./emsdk install ${{ inputs.version }} ./emsdk activate ${{ inputs.version }}
./emsdk activate ${{ inputs.version }}
+70 -70
View File
@@ -1,70 +1,70 @@
# example: # example:
# - uses: ./.github/actions/get-llvm # - uses: ./.github/actions/get-llvm
# with: # with:
# target: x86_64-unknown-linux-gnu # target: x86_64-unknown-linux-gnu
name: "Download LLVM" name: "Download LLVM"
inputs: inputs:
target: target:
required: true required: true
runs: runs:
using: "composite" using: "composite"
steps: steps:
- name: find asset - name: find asset
id: find id: find
uses: actions/github-script@v7 uses: actions/github-script@v7
env: env:
target: ${{ inputs.target }} target: ${{ inputs.target }}
with: with:
result-encoding: string result-encoding: string
script: | script: |
let page = 1; let page = 1;
let releases = []; let releases = [];
let releasePrefix = "llvm-" let releasePrefix = "llvm-"
let target = process.env.target let target = process.env.target
do { do {
const res = await github.rest.repos.listReleases({ const res = await github.rest.repos.listReleases({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
per_page: 50, per_page: 50,
page, page,
}); });
releases = res.data releases = res.data
releases.sort((a, b) => { releases.sort((a, b) => {
return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0); return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0);
}); });
let llvmLatestRelease = releases.find(release => { let llvmLatestRelease = releases.find(release => {
return release.tag_name.startsWith(releasePrefix); return release.tag_name.startsWith(releasePrefix);
}); });
if (llvmLatestRelease){ if (llvmLatestRelease){
let asset = llvmLatestRelease.assets.find(asset =>{ let asset = llvmLatestRelease.assets.find(asset =>{
return asset.name.includes(target); return asset.name.includes(target);
}); });
if (!asset){ if (!asset){
core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`); core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`);
process.exit(); process.exit();
} }
return asset.browser_download_url; return asset.browser_download_url;
} }
page++; page++;
} while(releases.length > 0); } while(releases.length > 0);
core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`); core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`);
process.exit(); process.exit();
- name: download - name: download
shell: bash shell: bash
run: | run: |
curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }} curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }}
- name: unpack - name: unpack
shell: bash shell: bash
run: | run: |
tar -xf llvm.tar.gz tar -xf llvm.tar.gz
rm llvm.tar.gz rm llvm.tar.gz
+49
View File
@@ -0,0 +1,49 @@
name: NPM Release
on:
release:
types: [released]
env:
CI: true
jobs:
publish:
name: Build & Publish to NPM
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Install
run: yarn install --immutable
- name: update-resolc
run: yarn update-resolc
- name: Build
run: yarn build
- name: Set version
run: npm version --no-git-tag-version ${{github.event.release.tag_name}}
- name: npm pack
run: npm pack
- uses: actions/upload-artifact@v4
with:
name: package
path: 'parity-revive-*.tgz'
- uses: octokit/request-action@bbedc70b1981e610d89f1f8de88311a1fc02fb83
with:
route: POST /repos/paritytech/npm_publish_automation/actions/workflows/publish.yml/dispatches
ref: main
inputs: '${{ format(''{{ "repo": "{0}", "run_id": "{1}" }}'', github.repository, github.run_id) }}'
env:
GITHUB_TOKEN: ${{ secrets.NPM_PUBLISH_AUTOMATION_TOKEN }}
+9 -6
View File
@@ -37,7 +37,15 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
target: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, wasm32-unknown-emscripten, aarch64-apple-darwin, x86_64-apple-darwin, x86_64-pc-windows-msvc] target:
[
x86_64-unknown-linux-gnu,
x86_64-unknown-linux-musl,
wasm32-unknown-emscripten,
aarch64-apple-darwin,
x86_64-apple-darwin,
x86_64-pc-windows-msvc,
]
include: include:
- target: x86_64-unknown-linux-gnu - target: x86_64-unknown-linux-gnu
builder-arg: gnu builder-arg: gnu
@@ -87,11 +95,6 @@ jobs:
run: | run: |
brew install ninja brew install ninja
- name: Install Dependencies
if: ${{ matrix.host == 'windows' }}
run: |
choco install ninja
- name: Install LLVM Builder - name: Install LLVM Builder
run: | run: |
cargo install --path crates/llvm-builder cargo install --path crates/llvm-builder
-46
View File
@@ -286,49 +286,3 @@ jobs:
resolc.wasm resolc.wasm
resolc_web.js resolc_web.js
checksums.txt checksums.txt
npm-release:
needs: [create-release]
runs-on: macos-14
environment: tags
steps:
- uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- run: npm ci -w js/resolc
- name: Build
run: |
cp -f resolc.{wasm,js} js/resolc/src/resolc
npm -w js/resolc run build
- name: Set version
run: |
VERSION="${{ github.event.release.tag_name }}"
STRIPPED_VERSION="${VERSION#v}"
npm -w js/resolc version --no-git-tag-version "$STRIPPED_VERSION"
- name: npm pack
run: npm -w js/resolc pack
- uses: actions/upload-artifact@v4
with:
name: npm_package
path: "parity-resolc-*.tgz"
- uses: octokit/request-action@bbedc70b1981e610d89f1f8de88311a1fc02fb83
with:
route: POST /repos/paritytech/npm_publish_automation/actions/workflows/publish.yml/dispatches
ref: main
inputs: '${{ format(''{{ "artifact_name": "npm_package", "repo": "{0}", "run_id": "{1}" }}'', github.repository, github.run_id) }}'
env:
GITHUB_TOKEN: ${{ secrets.NPM_PUBLISH_AUTOMATION_TOKEN }}
+3 -3
View File
@@ -4,9 +4,9 @@ on:
branches: ["main"] branches: ["main"]
types: [opened, synchronize] types: [opened, synchronize]
paths: paths:
- 'LLVM.lock' - "LLVM.lock"
- 'crates/llvm-builder/**' - "crates/llvm-builder/**"
- '.github/workflows/test-llvm-builder.yml' - ".github/workflows/test-llvm-builder.yml"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+7 -12
View File
@@ -86,18 +86,13 @@ jobs:
- name: Install Node Packages - name: Install Node Packages
run: npm install run: npm install
- name: Test emscripten - name: Run Playwright tests
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
run: | run: |
echo "Running tests for ${{ matrix.os }}" echo "Running tests for ${{ matrix.os }}"
npm run test:wasm npm run test:wasm
- name: Test @parity/resolc
run: |
echo "Running tests for ${{ matrix.os }}"
npm run -w js/resolc test
- name: Run Playwright tests
run: |
cd js/emscripten
npx playwright install --with-deps
npx playwright test
+1
View File
@@ -12,6 +12,7 @@ target-llvm
node_modules node_modules
artifacts artifacts
tmp tmp
package-lock.json
/*.html /*.html
/build /build
soljson.js soljson.js
-6
View File
@@ -1,6 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
+29 -10
View File
@@ -6,16 +6,9 @@ This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
## v0.1.0
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added ### Added
### Changed ### Changed
- By default, heavy size optimizations are applied.
### Fixed ### Fixed
@@ -26,13 +19,16 @@ This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added ### Added
- The `revive-runner` helper utility binary which helps to run contracts locally without a blockchain node.
- The `revive-runner` helper utility binary which helps to run contracts locally without a blockchain node.
- Allow configuration of the EVM heap memory size and stack size via CLI flags and JSON input settings. - Allow configuration of the EVM heap memory size and stack size via CLI flags and JSON input settings.
### Changed ### Changed
- The default PVM stack memory size was increased from 16kb to 32kb. - The default PVM stack memory size was increased from 16kb to 32kb.
### Fixed ### Fixed
- Constructors avoid storing zero sized immutable data on exit. - Constructors avoid storing zero sized immutable data on exit.
## v0.1.0-dev.13 ## v0.1.0-dev.13
@@ -42,16 +38,19 @@ This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added ### Added
- Support for solc v0.8.29 - Support for solc v0.8.29
- Decouples the solc JSON-input-output type definitions from the Solidity fronted and expose them via a dedicated crate. - Decouples the solc JSON-input-output type definitions from the Solidity fronted and expose them via a dedicated crate.
- `--supported-solc-versions` for `resolc` binary to return a `semver` range of supported `solc` versions. - `--supported-solc-versions` for `resolc` binary to return a `semver` range of supported `solc` versions.
- Support for passing LLVM command line options via the prcoess input or providing one or more `--llvm-arg='..'` resolc CLI flag. This allows more fine-grained control over the LLVM backend configuration. - Support for passing LLVM command line options via the prcoess input or providing one or more `--llvm-arg='..'` resolc CLI flag. This allows more fine-grained control over the LLVM backend configuration.
### Changed ### Changed
- Storage keys and values are big endian. This was a pre-mature optimization because for the contract itself it this is a no-op and thus not observable. However we should consider the storage layout as part of the contract ABI. The endianness of transient storage values are still kept as-is. - Storage keys and values are big endian. This was a pre-mature optimization because for the contract itself it this is a no-op and thus not observable. However we should consider the storage layout as part of the contract ABI. The endianness of transient storage values are still kept as-is.
- Running `resolc` using webkit is no longer supported. - Running `resolc` using webkit is no longer supported.
### Fixed ### Fixed
- A missing byte swap for the create2 salt value. - A missing byte swap for the create2 salt value.
## v0.1.0-dev.12 ## v0.1.0-dev.12
@@ -61,10 +60,12 @@ This is a development pre-release.
Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9` Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9`
### Added ### Added
- Per file output selection for `--standard-json` mode. - Per file output selection for `--standard-json` mode.
- The `ir` output selection option for `--standard-json` mode. - The `ir` output selection option for `--standard-json` mode.
### Changed ### Changed
- Improved code size: Large contracts compile to smaller code blobs when enabling aggressive size optimizations (`-Oz`). - Improved code size: Large contracts compile to smaller code blobs when enabling aggressive size optimizations (`-Oz`).
### Fixed ### Fixed
@@ -80,6 +81,7 @@ Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Changed ### Changed
### Fixed ### Fixed
- A bug causing incorrect loads from the emulated EVM linear memory. - A bug causing incorrect loads from the emulated EVM linear memory.
- A missing integer truncate after switching to 64bit. - A missing integer truncate after switching to 64bit.
@@ -90,10 +92,12 @@ This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50` Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added ### Added
- Support for the `coinbase` opcode. - Support for the `coinbase` opcode.
- The resolc web JS version. - The resolc web JS version.
### Changed ### Changed
- Missing the `--overwrite` flag emits an error instead of a warning. - Missing the `--overwrite` flag emits an error instead of a warning.
- The `resolc` executable prints the help by default. - The `resolc` executable prints the help by default.
- Removed support for legacy EVM assembly (EVMLA) translation. - Removed support for legacy EVM assembly (EVMLA) translation.
@@ -103,6 +107,7 @@ Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
If detected, the re-entrant call flag is not set and 0 deposit limit is endowed. If detected, the re-entrant call flag is not set and 0 deposit limit is endowed.
### Fixed ### Fixed
- Solidity: Add the solc `--libraries` files to sources. - Solidity: Add the solc `--libraries` files to sources.
- A data race in tests. - A data race in tests.
- Fix `broken pipe` errors. - Fix `broken pipe` errors.
@@ -116,9 +121,11 @@ This is a development pre-release.
### Added ### Added
### Changed ### Changed
- Syscalls with more than 6 arguments now pack them into registers. - Syscalls with more than 6 arguments now pack them into registers.
### Fixed ### Fixed
- Remove reloading of the resolc.js file (fix issue with relative path in web worker) - Remove reloading of the resolc.js file (fix issue with relative path in web worker)
## v0.1.0-dev.8 ## v0.1.0-dev.8
@@ -126,15 +133,18 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
### Added ### Added
- The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency. - The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency.
- Initial support for running `resolc` in the browser. - Initial support for running `resolc` in the browser.
### Changed ### Changed
- Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`. - Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`.
- The minimum supported Rust version is `1.81.0`. - The minimum supported Rust version is `1.81.0`.
- Error out early instead of invoking `solc` with invalid base or include path flags. - Error out early instead of invoking `solc` with invalid base or include path flags.
### Fixed ### Fixed
- Decouple the LLVM target dependency from the LLVM host dependency. - Decouple the LLVM target dependency from the LLVM host dependency.
- Do not error out if no files and no errors were produced. This aligns resolc closer to solc. - Do not error out if no files and no errors were produced. This aligns resolc closer to solc.
- Fixes input normalization in the Wasm version. - Fixes input normalization in the Wasm version.
@@ -144,17 +154,20 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
### Added ### Added
- Implement the `GASPRICE` opcode. - Implement the `GASPRICE` opcode.
- Implement the `BASEFEE` opcode. - Implement the `BASEFEE` opcode.
- Implement the `GASLIMIT` opcode. - Implement the `GASLIMIT` opcode.
### Changed ### Changed
- The `GAS` opcode now returns the remaining `ref_time`. - The `GAS` opcode now returns the remaining `ref_time`.
- Contracts can now be supplied call data input of arbitrary size. - Contracts can now be supplied call data input of arbitrary size.
- Some syscalls now return the value in a register, slightly improving emitted contract code. - Some syscalls now return the value in a register, slightly improving emitted contract code.
- Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit. - Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit.
### Fixed ### Fixed
- A linker bug which was preventing certain contracts from linking with the PVM linker. - A linker bug which was preventing certain contracts from linking with the PVM linker.
- JS: Fix encoding conversion from JS string (UTF-16) to UTF-8. - JS: Fix encoding conversion from JS string (UTF-16) to UTF-8.
- The git commit hash slug is always displayed in the version string. - The git commit hash slug is always displayed in the version string.
@@ -164,6 +177,7 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
# Added # Added
- Implement the `BLOCKHASH` opcode. - Implement the `BLOCKHASH` opcode.
- Implement delegate calls. - Implement delegate calls.
- Implement the `GASPRICE` opcode. Currently hard-coded to return `1`. - Implement the `GASPRICE` opcode. Currently hard-coded to return `1`.
@@ -171,21 +185,24 @@ This is a development pre-release.
- Initial support for emitting debug info (opt in via the `-g` flag) - Initial support for emitting debug info (opt in via the `-g` flag)
# Changed # Changed
- resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time. - resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time.
- The RISC-V bit-manipulation target feature (`zbb`) is enabled. - The RISC-V bit-manipulation target feature (`zbb`) is enabled.
# Fixed # Fixed
- Compilation to Wasm (for usage in node and web browsers)
- Compilation to Wasm (for usage in node and web browsers)
## v0.1.0-dev.5 ## v0.1.0-dev.5
This is development pre-release. This is development pre-release.
# Added # Added
- Implement the `CODESIZE` and `EXTCODESIZE` opcodes. - Implement the `CODESIZE` and `EXTCODESIZE` opcodes.
# Changed # Changed
- Include the full revive version in the contract metadata. - Include the full revive version in the contract metadata.
# Fixed # Fixed
@@ -195,9 +212,11 @@ This is development pre-release.
This is development pre-release. This is development pre-release.
# Added # Added
- Support the `ORIGIN` opcode. - Support the `ORIGIN` opcode.
# Changed # Changed
- Update polkavm to `v0.14.0`. - Update polkavm to `v0.14.0`.
- Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts. - Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts.
+4 -1
View File
@@ -92,4 +92,7 @@ clean:
rm -rf node_modules ; \ rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \ rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \ cargo uninstall revive-solidity ; \
cargo uninstall revive-llvm-builder ; cargo uninstall revive-llvm-builder ; \
rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
+7 -3
View File
@@ -14,6 +14,7 @@ This is experimental software in active development and not ready just yet for p
Discussion around the development is hosted on the [Polkadot Forum](https://forum.polkadot.network/t/contracts-update-solidity-on-polkavm/6949#a-new-solidity-compiler-1). Discussion around the development is hosted on the [Polkadot Forum](https://forum.polkadot.network/t/contracts-update-solidity-on-polkavm/6949#a-new-solidity-compiler-1).
## Installation ## Installation
Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions. Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions.
## Building from source ## Building from source
@@ -30,11 +31,13 @@ Building revive requires a [stable Rust installation](https://rustup.rs/) and a
Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases. Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases.
> **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive: > **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive:
>
> ```sh > ```sh
> xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/* > xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/*
> ``` > ```
After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it: After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it:
```sh ```sh
export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final
``` ```
@@ -44,7 +47,7 @@ export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/targ
<details> <details>
<summary>Building from source</summary> <summary>Building from source</summary>
Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards. Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards.
The `Makefile` provides a shortcut target to obtain a compatible LLVM build: The `Makefile` provides a shortcut target to obtain a compatible LLVM build:
@@ -93,11 +96,12 @@ make test-wasm
## Development ## Development
Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks. Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks.
Ensure that your branch passes `make test` locally when submitting a pull request. Ensure that your branch passes `make test` locally when submitting a pull request.
### Design overview ### Design overview
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
[Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc` (the project started as a fork of the era compiler). [Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc` (the project started as a fork of the era compiler).
+1 -1
View File
@@ -18,4 +18,4 @@ To create a new pre-release:
To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`. To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`.
Version suffix will be resolved automatically. Version suffix will be resolved automatically.
The workflows will create new GitHub release, and upload LLVM binaries. The workflows will create new GitHub release, and upload LLVM binaries.
Next release of resolc will use newly created binaries. Next release of resolc will use newly created binaries.
+1 -1
View File
@@ -1 +1 @@
https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md
+42 -42
View File
@@ -3,71 +3,71 @@
## Table of Contents ## Table of Contents
- [Benchmark Results](#benchmark-results) - [Benchmark Results](#benchmark-results)
- [Baseline](#baseline) - [Baseline](#baseline)
- [OddPorduct](#oddporduct) - [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber) - [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive) - [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative) - [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet) - [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1) - [SHA1](#sha1)
## Benchmark Results ## Benchmark Results
### Baseline ### Baseline
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:--------|:-------------------------|:-------------------------------- | | :------ | :------------------------ | :------------------------------- |
| **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) | | **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) |
### OddPorduct ### OddPorduct
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:-------------|:--------------------------|:-------------------------------- | | :----------- | :------------------------- | :------------------------------- |
| **`10000`** | `3.60 ms` (✅ **1.00x**) | `1.57 ms` (🚀 **2.28x faster**) | | **`10000`** | `3.60 ms` (✅ **1.00x**) | `1.57 ms` (🚀 **2.28x faster**) |
| **`100000`** | `34.72 ms` (✅ **1.00x**) | `14.82 ms` (🚀 **2.34x faster**) | | **`100000`** | `34.72 ms` (✅ **1.00x**) | `14.82 ms` (🚀 **2.34x faster**) |
| **`300000`** | `105.01 ms` (✅ **1.00x**) | `44.11 ms` (🚀 **2.38x faster**) | | **`300000`** | `105.01 ms` (✅ **1.00x**) | `44.11 ms` (🚀 **2.38x faster**) |
### TriangleNumber ### TriangleNumber
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- | | :----------- | :------------------------ | :------------------------------- |
| **`10000`** | `2.43 ms` (✅ **1.00x**) | `1.12 ms` (🚀 **2.17x faster**) | | **`10000`** | `2.43 ms` (✅ **1.00x**) | `1.12 ms` (🚀 **2.17x faster**) |
| **`100000`** | `24.20 ms` (✅ **1.00x**) | `10.86 ms` (🚀 **2.23x faster**) | | **`100000`** | `24.20 ms` (✅ **1.00x**) | `10.86 ms` (🚀 **2.23x faster**) |
| **`360000`** | `88.69 ms` (✅ **1.00x**) | `38.46 ms` (🚀 **2.31x faster**) | | **`360000`** | `88.69 ms` (✅ **1.00x**) | `38.46 ms` (🚀 **2.31x faster**) |
### FibonacciRecursive ### FibonacciRecursive
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:---------|:--------------------------|:--------------------------------- | | :------- | :------------------------- | :-------------------------------- |
| **`12`** | `144.17 us` (✅ **1.00x**) | `150.85 us` (✅ **1.05x slower**) | | **`12`** | `144.17 us` (✅ **1.00x**) | `150.85 us` (✅ **1.05x slower**) |
| **`16`** | `938.71 us` (✅ **1.00x**) | `922.11 us` (✅ **1.02x faster**) | | **`16`** | `938.71 us` (✅ **1.00x**) | `922.11 us` (✅ **1.02x faster**) |
| **`20`** | `6.54 ms` (✅ **1.00x**) | `6.20 ms` (✅ **1.05x faster**) | | **`20`** | `6.54 ms` (✅ **1.00x**) | `6.20 ms` (✅ **1.05x faster**) |
| **`24`** | `45.73 ms` (✅ **1.00x**) | `41.98 ms` (✅ **1.09x faster**) | | **`24`** | `45.73 ms` (✅ **1.00x**) | `41.98 ms` (✅ **1.09x faster**) |
### FibonacciIterative ### FibonacciIterative
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- | | :-------- | :------------------------ | :------------------------------- |
| **`64`** | `23.00 us` (✅ **1.00x**) | `31.88 us` (❌ *1.39x slower*) | | **`64`** | `23.00 us` (✅ **1.00x**) | `31.88 us` (❌ _1.39x slower_) |
| **`128`** | `35.28 us` (✅ **1.00x**) | `42.43 us` (❌ *1.20x slower*) | | **`128`** | `35.28 us` (✅ **1.00x**) | `42.43 us` (❌ _1.20x slower_) |
| **`256`** | `60.12 us` (✅ **1.00x**) | `61.20 us` (✅ **1.02x slower**) | | **`256`** | `60.12 us` (✅ **1.00x**) | `61.20 us` (✅ **1.02x slower**) |
### FibonacciBinet ### FibonacciBinet
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- | | :-------- | :------------------------ | :----------------------------- |
| **`64`** | `23.01 us` (✅ **1.00x**) | `47.74 us` (❌ *2.07x slower*) | | **`64`** | `23.01 us` (✅ **1.00x**) | `47.74 us` (❌ _2.07x slower_) |
| **`128`** | `25.44 us` (✅ **1.00x**) | `49.67 us` (❌ *1.95x slower*) | | **`128`** | `25.44 us` (✅ **1.00x**) | `49.67 us` (❌ _1.95x slower_) |
| **`256`** | `28.66 us` (✅ **1.00x**) | `53.01 us` (❌ *1.85x slower*) | | **`256`** | `28.66 us` (✅ **1.00x**) | `53.01 us` (❌ _1.85x slower_) |
### SHA1 ### SHA1
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:--------------------------|:--------------------------------- | | :-------- | :------------------------- | :------------------------------ |
| **`1`** | `135.87 us` (✅ **1.00x**) | `243.75 us` (❌ *1.79x slower*) | | **`1`** | `135.87 us` (✅ **1.00x**) | `243.75 us` (❌ _1.79x slower_) |
| **`64`** | `258.45 us` (✅ **1.00x**) | `355.70 us` (❌ *1.38x slower*) | | **`64`** | `258.45 us` (✅ **1.00x**) | `355.70 us` (❌ _1.38x slower_) |
| **`512`** | `1.10 ms` (✅ **1.00x**) | `1.09 ms` (✅ **1.01x faster**) | | **`512`** | `1.10 ms` (✅ **1.00x**) | `1.09 ms` (✅ **1.01x faster**) |
--- ---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+45 -45
View File
@@ -1,47 +1,47 @@
{ {
"config": { "config": {
"chainId": 420420420, "chainId": 420420420,
"homesteadBlock": 0, "homesteadBlock": 0,
"eip150Block": 0, "eip150Block": 0,
"eip155Block": 0, "eip155Block": 0,
"eip158Block": 0, "eip158Block": 0,
"byzantiumBlock": 0, "byzantiumBlock": 0,
"constantinopleBlock": 0, "constantinopleBlock": 0,
"petersburgBlock": 0, "petersburgBlock": 0,
"istanbulBlock": 0, "istanbulBlock": 0,
"berlinBlock": 0, "berlinBlock": 0,
"londonBlock": 0, "londonBlock": 0,
"arrowGlacierBlock": 0, "arrowGlacierBlock": 0,
"grayGlacierBlock": 0, "grayGlacierBlock": 0,
"shanghaiTime": 0, "shanghaiTime": 0,
"cancunTime": 0, "cancunTime": 0,
"terminalTotalDifficulty": 0, "terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true, "terminalTotalDifficultyPassed": true,
"blobSchedule": { "blobSchedule": {
"cancun": { "cancun": {
"target": 3, "target": 3,
"max": 6, "max": 6,
"baseFeeUpdateFraction": 3338477 "baseFeeUpdateFraction": 3338477
} }
}
},
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
} }
} },
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
}
}
+1 -1
View File
@@ -7,4 +7,4 @@
"FibonacciIterative": 1497, "FibonacciIterative": 1497,
"Flipper": 2099, "Flipper": 2099,
"SHA1": 8243 "SHA1": 8243
} }
+64 -55
View File
@@ -6,6 +6,7 @@ Parity fork of the [Matter Labs zksync LLVM builder](https://github.com/matter-l
The LLVM compiler framework for revive must be built with our tool called `revive-llvm`. The LLVM compiler framework for revive must be built with our tool called `revive-llvm`.
This is because the revive compiler has requirements not fullfilled in upstream builds: This is because the revive compiler has requirements not fullfilled in upstream builds:
- Special builds for compiling the frontend into statically linked ELF binaries and also Wasm executables - Special builds for compiling the frontend into statically linked ELF binaries and also Wasm executables
- The RISC-V target (the PolkaVM target) - The RISC-V target (the PolkaVM target)
- The compiler-rt builtins for the PolkaVM target - The compiler-rt builtins for the PolkaVM target
@@ -17,85 +18,92 @@ Obtain a compatible build for your host platform from the release section of thi
<details> <details>
<summary>1. Install the system prerequisites.</summary> <summary>1. Install the system prerequisites.</summary>
* Linux (Debian): - Linux (Debian):
Install the following packages: Install the following packages:
```shell
apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
```
* Linux (Arch):
Install the following packages: ```shell
```shell apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
pacman -Syu which cmake ninja curl git pkg-config clang lld ```
```
* MacOS: - Linux (Arch):
* Install the [HomeBrew](https://brew.sh) package manager. Install the following packages:
* Install the following packages:
```shell ```shell
brew install cmake ninja coreutils pacman -Syu which cmake ninja curl git pkg-config clang lld
``` ```
* Install your choice of a recent LLVM/[Clang](https://clang.llvm.org) compiler, e.g. via [Xcode](https://developer.apple.com/xcode/), [Apples Command Line Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html), or your preferred package manager. - MacOS:
</details>
* Install the [HomeBrew](https://brew.sh) package manager.
* Install the following packages:
```shell
brew install cmake ninja coreutils
```
* Install your choice of a recent LLVM/[Clang](https://clang.llvm.org) compiler, e.g. via [Xcode](https://developer.apple.com/xcode/), [Apples Command Line Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html), or your preferred package manager.
</details>
<details> <details>
<summary>2. Install Rust.</summary> <summary>2. Install Rust.</summary>
* Follow the latest [official instructions](https://www.rust-lang.org/tools/install: - Follow the latest [official instructions](https://www.rust-lang.org/tools/install:
```shell `shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. ${HOME}/.cargo/env . ${HOME}/.cargo/env
``` `
> Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform. > Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform.
</details>
</details>
<details> <details>
<summary>3. Install the revive LLVM framework builder.</summary> <summary>3. Install the revive LLVM framework builder.</summary>
* Install the builder using `cargo`: - Install the builder using `cargo`:
```shell
cargo install --git https://github.com/paritytech/revive-llvm-builder --force --locked
```
> The builder is not the LLVM framework itself, but a tool that clones its repository and runs a sequence of build commands. By default it is installed in `~/.cargo/bin/`, which is recommended to be added to your `$PATH`. ```shell
cargo install --git https://github.com/paritytech/revive-llvm-builder --force --locked
```
> The builder is not the LLVM framework itself, but a tool that clones its repository and runs a sequence of build commands. By default it is installed in `~/.cargo/bin/`, which is recommended to be added to your `$PATH`.
</details> </details>
<details> <details>
<summary>4. (Optional) Create the `LLVM.lock` file.</summary> <summary>4. (Optional) Create the `LLVM.lock` file.</summary>
* The `LLVM.lock` dictates the LLVM source tree being used. - The `LLVM.lock` dictates the LLVM source tree being used.
A default `./LLVM.lock` pointing to the release used for development is already provided. A default `./LLVM.lock` pointing to the release used for development is already provided.
</details> </details>
<details> <details>
<summary>5. Build LLVM.</summary> <summary>5. Build LLVM.</summary>
* Clone and build the LLVM framework using the `revive-llvm` tool. - Clone and build the LLVM framework using the `revive-llvm` tool.
The clang and lld projects are required for the `resolc` Solidity frontend executable; they are enabled by default. LLVM assertions are also enabled by default. The clang and lld projects are required for the `resolc` Solidity frontend executable; they are enabled by default. LLVM assertions are also enabled by default.
```shell ```shell
revive-llvm clone revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang revive-llvm build --llvm-projects lld --llvm-projects clang
``` ```
Build artifacts end up in the `./target-llvm/gnu/target-final/` directory by default. Build artifacts end up in the `./target-llvm/gnu/target-final/` directory by default.
The `gnu` directory depends on the supported archticture and will either be `gnu`, `musl` or `emscripten`. The `gnu` directory depends on the supported archticture and will either be `gnu`, `musl` or `emscripten`.
You now need to export the final target directory `$LLVM_SYS_181_PREFIX`: `export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final` You now need to export the final target directory `$LLVM_SYS_181_PREFIX`: `export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final`
If built with the `--enable-tests` option, test tools will be in the `./target-llvm/gnu/build-final/` directory, along with copies of the build artifacts. For all supported build options, run `revive-llvm build --help`. If built with the `--enable-tests` option, test tools will be in the `./target-llvm/gnu/build-final/` directory, along with copies of the build artifacts. For all supported build options, run `revive-llvm build --help`.
</details> </details>
## Supported target architectures ## Supported target architectures
The following target platforms are supported: The following target platforms are supported:
- Linux GNU (x86) - Linux GNU (x86)
- Linux MUSL (x86) - Linux MUSL (x86)
- MacOS (aarch64) - MacOS (aarch64)
@@ -105,28 +113,29 @@ The following target platforms are supported:
<details> <details>
<summary>Building for MUSL</summary> <summary>Building for MUSL</summary>
* Via a musl build we can build revive into fully static ELF binaries. - Via a musl build we can build revive into fully static ELF binaries.
Which is desirable for reproducible Solidity contracts builds. Which is desirable for reproducible Solidity contracts builds.
The resulting binary is also very portable, akin to the`solc` frontend binary distribution. The resulting binary is also very portable, akin to the`solc` frontend binary distribution.
Clone and build the LLVM framework using the `revive-llvm` tool: Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env musl clone ```shell
revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld revive-llvm --target-env musl clone
``` revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details> </details>
<details> <details>
<summary>Building for Emscripten</summary> <summary>Building for Emscripten</summary>
* Via an emsdk build we can run revive in the browser and on node.js. - Via an emsdk build we can run revive in the browser and on node.js.
Clone and build the LLVM framework using the `revive-llvm` tool: Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env emscripten clone ```shell
revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld revive-llvm --target-env emscripten clone
``` revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details> </details>
+8 -2
View File
@@ -117,7 +117,13 @@ pub fn build(
log::info!("building compiler-rt for rv64emac"); log::info!("building compiler-rt for rv64emac");
crate::utils::check_presence("cmake")?; crate::utils::check_presence("cmake")?;
crate::utils::check_presence("ninja")?;
let generator = if cfg!(target_os = "windows") {
"Visual Studio 17 2022"
} else {
crate::utils::check_presence("ninja")?;
"Ninja"
};
let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?; let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?;
let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?; let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?;
@@ -130,7 +136,7 @@ pub fn build(
"-B", "-B",
llvm_compiler_rt_build.to_string_lossy().as_ref(), llvm_compiler_rt_build.to_string_lossy().as_ref(),
"-G", "-G",
"Ninja", generator,
]) ])
.args(CMAKE_STATIC_ARGS) .args(CMAKE_STATIC_ARGS)
.args(cmake_dynamic_args(build_type, target_env)?) .args(cmake_dynamic_args(build_type, target_env)?)
@@ -234,7 +234,7 @@ impl TryFrom<&SolcStandardJsonInputSettingsOptimizer> for Settings {
fn try_from(value: &SolcStandardJsonInputSettingsOptimizer) -> Result<Self, Self::Error> { fn try_from(value: &SolcStandardJsonInputSettingsOptimizer) -> Result<Self, Self::Error> {
let mut result = match value.mode { let mut result = match value.mode {
Some(mode) => Self::try_from_cli(mode)?, Some(mode) => Self::try_from_cli(mode)?,
None => Self::size(), None => Self::cycles(),
}; };
if value.fallback_to_optimizing_for_size.unwrap_or_default() { if value.fallback_to_optimizing_for_size.unwrap_or_default() {
result.enable_fallback_to_size(); result.enable_fallback_to_size();
+2 -1
View File
@@ -16,7 +16,7 @@ Inside the root `revive` repository directory, execute:
make install-revive-runner make install-revive-runner
``` ```
Which will install the `revive-runner` using `cargo`. Which will install the `revive-runner` using `cargo`.
## Usage ## Usage
@@ -24,3 +24,4 @@ Set the `RUST_LOG` environment varibale to the `trace` level to see the full Pol
```bash ```bash
RUST_LOG=trace revive-runner -f mycontract.pvm -c a9059cbb000000000000000000000000f24ff3a9cf04c71dbc94d0b566f7a27b94566cac0000000000000000000000000000000000000000000000000000000000000000 RUST_LOG=trace revive-runner -f mycontract.pvm -c a9059cbb000000000000000000000000f24ff3a9cf04c71dbc94d0b566f7a27b94566cac0000000000000000000000000000000000000000000000000000000000000000
```
+1 -1
View File
@@ -129,7 +129,7 @@ fn main_inner() -> anyhow::Result<()> {
let mut optimizer_settings = match arguments.optimization { let mut optimizer_settings = match arguments.optimization {
Some(mode) => revive_llvm_context::OptimizerSettings::try_from_cli(mode)?, Some(mode) => revive_llvm_context::OptimizerSettings::try_from_cli(mode)?,
None => revive_llvm_context::OptimizerSettings::size(), None => revive_llvm_context::OptimizerSettings::cycles(),
}; };
if arguments.fallback_to_optimizing_for_size { if arguments.fallback_to_optimizing_for_size {
optimizer_settings.enable_fallback_to_size(); optimizer_settings.enable_fallback_to_size();
@@ -23,4 +23,4 @@
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }
File diff suppressed because one or more lines are too long
@@ -1,33 +1,33 @@
import * as path from 'path' import * as path from "path";
const outputDir = 'artifacts' const outputDir = "artifacts";
const binExtension = ':C.pvm' const binExtension = ":C.pvm";
const asmExtension = ':C.pvmasm' const asmExtension = ":C.pvmasm";
const llvmExtension = '.ll' const llvmExtension = ".ll";
const contractSolFilename = 'contract.sol' const contractSolFilename = "contract.sol";
const contractYulFilename = 'contract.yul' const contractYulFilename = "contract.yul";
const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized' const contractOptimizedLLVMFilename = contractSolFilename + ".C.optimized";
const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized' const contractUnoptimizedLLVMFilename = contractSolFilename + ".C.unoptimized";
const pathToOutputDir = path.join(__dirname, '..', outputDir) const pathToOutputDir = path.join(__dirname, "..", outputDir);
const pathToContracts = path.join(__dirname, '..', 'src', 'contracts') const pathToContracts = path.join(__dirname, "..", "src", "contracts");
const pathToBasicYulContract = path.join( const pathToBasicYulContract = path.join(
pathToContracts, pathToContracts,
'yul', "yul",
contractYulFilename contractYulFilename,
) );
const pathToBasicSolContract = path.join( const pathToBasicSolContract = path.join(
pathToContracts, pathToContracts,
'solidity', "solidity",
contractSolFilename contractSolFilename,
) );
const pathToSolBinOutputFile = path.join( const pathToSolBinOutputFile = path.join(
pathToOutputDir, pathToOutputDir,
contractSolFilename + binExtension contractSolFilename + binExtension,
) );
const pathToSolAsmOutputFile = path.join( const pathToSolAsmOutputFile = path.join(
pathToOutputDir, pathToOutputDir,
contractSolFilename + asmExtension contractSolFilename + asmExtension,
) );
export const paths = { export const paths = {
outputDir: outputDir, outputDir: outputDir,
@@ -44,4 +44,4 @@ export const paths = {
pathToBasicYulContract: pathToBasicYulContract, pathToBasicYulContract: pathToBasicYulContract,
pathToSolBinOutputFile: pathToSolBinOutputFile, pathToSolBinOutputFile: pathToSolBinOutputFile,
pathToSolAsmOutputFile: pathToSolAsmOutputFile, pathToSolAsmOutputFile: pathToSolAsmOutputFile,
} };
@@ -1,51 +1,51 @@
import * as shell from 'shelljs' import * as shell from "shelljs";
import * as fs from 'fs' import * as fs from "fs";
import { spawnSync } from 'child_process' import { spawnSync } from "child_process";
interface CommandResult { interface CommandResult {
output: string output: string;
exitCode: number exitCode: number;
} }
export const executeCommand = ( export const executeCommand = (
command: string, command: string,
stdin?: string stdin?: string,
): CommandResult => { ): CommandResult => {
if (stdin) { if (stdin) {
const process = spawnSync(command, [], { const process = spawnSync(command, [], {
input: stdin, input: stdin,
shell: true, shell: true,
encoding: 'utf8', encoding: "utf8",
maxBuffer: 30 * 1024 * 1024, maxBuffer: 30 * 1024 * 1024,
}) });
return { return {
exitCode: process.status || 0, exitCode: process.status || 0,
output: (process.stdout || process.stderr || '').toString(), output: (process.stdout || process.stderr || "").toString(),
} };
} }
const result = shell.exec(command, { silent: true, async: false }) const result = shell.exec(command, { silent: true, async: false });
return { return {
exitCode: result.code, exitCode: result.code,
output: result.stdout || result.stderr || '', output: result.stdout || result.stderr || "",
} };
} };
export const isFolderExist = (folder: string): boolean => { export const isFolderExist = (folder: string): boolean => {
return shell.test('-d', folder) return shell.test("-d", folder);
} };
export const isFileExist = ( export const isFileExist = (
pathToFileDir: string, pathToFileDir: string,
fileName: string, fileName: string,
fileExtension: string fileExtension: string,
): boolean => { ): boolean => {
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension) return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension);
} };
export const isFileEmpty = (file: string): boolean => { export const isFileEmpty = (file: string): boolean => {
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
return fs.readFileSync(file).length === 0 return fs.readFileSync(file).length === 0;
} }
} };
@@ -1,44 +1,44 @@
import { executeCommand } from '../src/helper' import { executeCommand } from "../src/helper";
import { paths } from '../src/entities' import { paths } from "../src/entities";
//id1746 //id1746
describe('Run with --asm by default', () => { describe("Run with --asm by default", () => {
const command = `resolc ${paths.pathToBasicSolContract} --asm` const command = `resolc ${paths.pathToBasicSolContract} --asm`;
const result = executeCommand(command) const result = executeCommand(command);
const commandInvalid = 'resolc --asm' const commandInvalid = "resolc --asm";
const resultInvalid = executeCommand(commandInvalid) const resultInvalid = executeCommand(commandInvalid);
it('Valid command exit code = 0', () => { it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('--asm output is presented', () => { it("--asm output is presented", () => {
const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i] const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i];
for (const pattern of expectedPatterns) { for (const pattern of expectedPatterns) {
expect(result.output).toMatch(pattern) expect(result.output).toMatch(pattern);
} }
}) });
it('solc exit code == resolc exit code', () => { it("solc exit code == resolc exit code", () => {
const command = `solc ${paths.pathToBasicSolContract} --asm` const command = `solc ${paths.pathToBasicSolContract} --asm`;
const solcResult = executeCommand(command) const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
it('run invalid: resolc --asm', () => { it("run invalid: resolc --asm", () => {
expect(resultInvalid.output).toMatch( expect(resultInvalid.output).toMatch(
/(No input sources specified|Compilation aborted)/i /(No input sources specified|Compilation aborted)/i,
) );
}) });
it('Invalid command exit code = 1', () => { it("Invalid command exit code = 1", () => {
expect(resultInvalid.exitCode).toBe(1) expect(resultInvalid.exitCode).toBe(1);
}) });
it('Invalid solc exit code == Invalid resolc exit code', () => { it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = 'solc --asm' const command = "solc --asm";
const solcResult = executeCommand(command) const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode) expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
}) });
}) });
@@ -3,239 +3,239 @@ import {
isFolderExist, isFolderExist,
isFileExist, isFileExist,
isFileEmpty, isFileEmpty,
} from '../src/helper' } from "../src/helper";
import { paths } from '../src/entities' import { paths } from "../src/entities";
import * as shell from 'shelljs' import * as shell from "shelljs";
import * as path from 'path' import * as path from "path";
//id1762 //id1762
describe('Run resolc without any options', () => { describe("Run resolc without any options", () => {
const command = 'resolc' const command = "resolc";
const result = executeCommand(command) const result = executeCommand(command);
it('Info with help is presented', () => { it("Info with help is presented", () => {
expect(result.output).toMatch(/(Usage: resolc)/i) expect(result.output).toMatch(/(Usage: resolc)/i);
}) });
it('Exit code = 1', () => { it("Exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('solc exit code == resolc exit code', () => { it("solc exit code == resolc exit code", () => {
const command = 'solc' const command = "solc";
const solcResult = executeCommand(command) const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
//#1713 //#1713
describe('Default run a command from the help', () => { 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 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) const result = executeCommand(command);
it('Compiler run successful', () => { it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i) expect(result.output).toMatch(/(Compiler run successful.)/i);
}) });
it('Exit code = 0', () => { it("Exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('Output dir is created', () => { it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true) expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}) });
xit('Output file is created', () => { xit("Output file is created", () => {
// a bug on windows // a bug on windows
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractSolFilename, paths.contractSolFilename,
paths.binExtension paths.binExtension,
) ),
).toBe(true) ).toBe(true);
}) });
it('the output file is not empty', () => { it("the output file is not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
}) });
it("No 'Error'/'Warning'/'Fail' in the output", () => { it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i) expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
}) });
}) });
//#1818 //#1818
describe('Default run a command from the help', () => { 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 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) const result = executeCommand(command);
it('Compiler run successful', () => { it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i) expect(result.output).toMatch(/(Compiler run successful.)/i);
}) });
it('Exit code = 0', () => { it("Exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('Output dir is created', () => { it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true) expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}) });
xit('Output files are created', () => { xit("Output files are created", () => {
// a bug on windows // a bug on windows
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractSolFilename, paths.contractSolFilename,
paths.binExtension paths.binExtension,
) ),
).toBe(true) ).toBe(true);
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractSolFilename, paths.contractSolFilename,
paths.asmExtension paths.asmExtension,
) ),
).toBe(true) ).toBe(true);
}) });
it('the output files are not empty', () => { it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
}) });
it("No 'Error'/'Warning'/'Fail' in the output", () => { it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i) expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
}) });
}) });
describe('Run resolc with source debug information', () => { describe("Run resolc with source debug information", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`, `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}"`, `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
] // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
const command = commands[idx] const command = commands[idx];
const result = executeCommand(command) const result = executeCommand(command);
it('Compiler run successful', () => { it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i) expect(result.output).toMatch(/(Compiler run successful.)/i);
}) });
it('Exit code = 0', () => { it("Exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('Output dir is created', () => { it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true) expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}) });
it('Output files are created', () => { it("Output files are created", () => {
// a bug on windows // a bug on windows
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractSolFilename, paths.contractSolFilename,
paths.binExtension paths.binExtension,
) ),
).toBe(true) ).toBe(true);
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractSolFilename, paths.contractSolFilename,
paths.asmExtension paths.asmExtension,
) ),
).toBe(true) ).toBe(true);
}) });
it('the output files are not empty', () => { it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
}) });
it("No 'Error'/'Fail' in the output", () => { it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i) expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
}) });
} }
}) });
describe('Run resolc with source debug information, check LLVM debug-info', () => { describe("Run resolc with source debug information, check LLVM debug-info", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${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`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
const command = commands[idx] const command = commands[idx];
const result = executeCommand(command) const result = executeCommand(command);
it('Compiler run successful', () => { it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i) expect(result.output).toMatch(/(Compiler run successful.)/i);
}) });
it('Exit code = 0', () => { it("Exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('Output dir is created', () => { it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true) expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}) });
it('Output files are created', () => { it("Output files are created", () => {
// a bug on windows // a bug on windows
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractOptimizedLLVMFilename, paths.contractOptimizedLLVMFilename,
paths.llvmExtension paths.llvmExtension,
) ),
).toBe(true) ).toBe(true);
expect( expect(
isFileExist( isFileExist(
paths.pathToOutputDir, paths.pathToOutputDir,
paths.contractUnoptimizedLLVMFilename, paths.contractUnoptimizedLLVMFilename,
paths.llvmExtension paths.llvmExtension,
) ),
).toBe(true) ).toBe(true);
}) });
it('the output files are not empty', () => { it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false) expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
}) });
it("No 'Error'/'Fail' in the output", () => { it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i) expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
}) });
} }
}) });
describe('Standard JSON compilation with path options', () => { describe("Standard JSON compilation with path options", () => {
const contractsDir = path.join(shell.tempdir(), 'contracts-test') const contractsDir = path.join(shell.tempdir(), "contracts-test");
const inputFile = path.join(__dirname, '..', 'src/contracts/compiled/1.json') const inputFile = path.join(__dirname, "..", "src/contracts/compiled/1.json");
beforeAll(() => { beforeAll(() => {
shell.mkdir('-p', contractsDir) shell.mkdir("-p", contractsDir);
const input = JSON.parse(shell.cat(inputFile).toString()) const input = JSON.parse(shell.cat(inputFile).toString());
Object.entries(input.sources).forEach( Object.entries(input.sources).forEach(
([sourcePath, source]: [string, any]) => { ([sourcePath, source]: [string, any]) => {
const filePath = path.join(contractsDir, sourcePath) const filePath = path.join(contractsDir, sourcePath);
shell.mkdir('-p', path.dirname(filePath)) shell.mkdir("-p", path.dirname(filePath));
shell.ShellString(source.content).to(filePath) shell.ShellString(source.content).to(filePath);
} },
) );
}) });
afterAll(() => { afterAll(() => {
shell.rm('-rf', contractsDir) shell.rm("-rf", contractsDir);
}) });
describe('Output with all path options', () => { describe("Output with all path options", () => {
let result: { exitCode: number; output: string } let result: { exitCode: number; output: string };
beforeAll(() => { beforeAll(() => {
const tempInputFile = path.join(contractsDir, 'temp-input.json') const tempInputFile = path.join(contractsDir, "temp-input.json");
shell.cp(inputFile, tempInputFile) shell.cp(inputFile, tempInputFile);
const inputContent = shell.cat(inputFile).toString() const inputContent = shell.cat(inputFile).toString();
const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"` const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`;
result = executeCommand(command, inputContent) result = executeCommand(command, inputContent);
shell.rm(tempInputFile) shell.rm(tempInputFile);
}) });
it('Compiler run successful without emiting warnings', () => { it("Compiler run successful without emiting warnings", () => {
const parsedResults = JSON.parse(result.output) const parsedResults = JSON.parse(result.output);
expect( expect(
parsedResults.errors.filter( parsedResults.errors.filter(
(error: { type: string }) => error.type != 'Warning' (error: { type: string }) => error.type != "Warning",
) ),
).toEqual([]) ).toEqual([]);
}) });
}) });
}) });
@@ -1,39 +1,39 @@
import { executeCommand } from '../src/helper' import { executeCommand } from "../src/helper";
import { paths } from '../src/entities' import { paths } from "../src/entities";
//id1743 //id1743
describe('Run with --yul by default', () => { describe("Run with --yul by default", () => {
const command = `resolc ${paths.pathToBasicYulContract} --yul` const command = `resolc ${paths.pathToBasicYulContract} --yul`;
const result = executeCommand(command) const result = executeCommand(command);
const commandInvalid = 'resolc --yul' const commandInvalid = "resolc --yul";
const resultInvalid = executeCommand(commandInvalid) const resultInvalid = executeCommand(commandInvalid);
it('Valid command exit code = 0', () => { it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('--yul output is presented', () => { it("--yul output is presented", () => {
expect(result.output).toMatch(/(Compiler run successful)/i) expect(result.output).toMatch(/(Compiler run successful)/i);
expect(result.output).toMatch(/(No output requested)/i) expect(result.output).toMatch(/(No output requested)/i);
}) });
xit('solc exit code == resolc exit code', () => { xit("solc exit code == resolc exit code", () => {
// unknown solc issue for datatype of the contract // unknown solc issue for datatype of the contract
const command = `solc ${paths.pathToBasicSolContract} --yul` const command = `solc ${paths.pathToBasicSolContract} --yul`;
const solcResult = executeCommand(command) const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
it('run invalid: resolc --yul', () => { it("run invalid: resolc --yul", () => {
expect(resultInvalid.output).toMatch(/(The input file is missing)/i) expect(resultInvalid.output).toMatch(/(The input file is missing)/i);
}) });
it('Invalid command exit code = 1', () => { it("Invalid command exit code = 1", () => {
expect(resultInvalid.exitCode).toBe(1) expect(resultInvalid.exitCode).toBe(1);
}) });
it('Invalid solc exit code == Invalid resolc exit code', () => { it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = 'solc --yul' const command = "solc --yul";
const solcResult = executeCommand(command) const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode) expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
}) });
}) });
@@ -2,6 +2,6 @@
"compilerOptions": { "compilerOptions": {
"target": "ES6", "target": "ES6",
"module": "CommonJS", "module": "CommonJS",
"outDir": "./dist", "outDir": "./dist"
} }
} }
+104 -100
View File
@@ -1,9 +1,9 @@
import { executeCommand } from '../src/helper' import { executeCommand } from "../src/helper";
import { paths } from '../src/entities' import { paths } from "../src/entities";
describe('Set of --combined-json tests', () => { describe("Set of --combined-json tests", () => {
const zksolcCommand = 'zksolc' const zksolcCommand = "zksolc";
const solcCommand = 'solc' const solcCommand = "solc";
const json_args: string[] = [ const json_args: string[] = [
`abi`, `abi`,
`hashes`, `hashes`,
@@ -15,45 +15,49 @@ describe('Set of --combined-json tests', () => {
`asm`, `asm`,
`bin`, `bin`,
`bin-runtime`, `bin-runtime`,
] ];
//id1742:I //id1742:I
describe(`Run ${zksolcCommand} with just --combined-json`, () => { describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const args = [`--combined-json`] const args = [`--combined-json`];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
it('Valid command exit code = 1', () => { it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i) expect(result.output).toMatch(
}) /(requires a value but none was supplied)/i,
);
});
it('solc exit code == zksolc exit code', () => { it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
//id1742:II //id1742:II
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => { describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`] const args = [`${paths.pathToBasicSolContract}`, `--combined-json`];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
it('Valid command exit code = 1', () => { it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i) expect(result.output).toMatch(
}) /(requires a value but none was supplied)/i,
);
});
it('solc exit code == zksolc exit code', () => { it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
//id1742:III //id1742:III
for (let i = 0; i < json_args.length; i++) { for (let i = 0; i < json_args.length; i++) {
@@ -62,22 +66,22 @@ describe('Set of --combined-json tests', () => {
`${paths.pathToBasicSolContract}`, `${paths.pathToBasicSolContract}`,
`--combined-json`, `--combined-json`,
`${json_args[i]}`, `${json_args[i]}`,
] ];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
it('Valid command exit code = 0', () => { it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(contracts)/i) expect(result.output).toMatch(/(contracts)/i);
}) });
it('solc exit code == zksolc exit code', () => { it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
} }
//id1829:I //id1829:I
@@ -87,22 +91,22 @@ describe('Set of --combined-json tests', () => {
`${paths.pathToBasicSolContract}`, `${paths.pathToBasicSolContract}`,
`--combined-json`, `--combined-json`,
`--${json_args[i]}`, `--${json_args[i]}`,
] ];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
it('Valid command exit code = 1', () => { it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(Invalid option|error)/i) expect(result.output).toMatch(/(Invalid option|error)/i);
}) });
it('solc exit code == zksolc exit code', () => { it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
} }
//id1829:II //id1829:II
@@ -113,24 +117,24 @@ describe('Set of --combined-json tests', () => {
`--combined-json`, `--combined-json`,
`${json_args[i]}`, `${json_args[i]}`,
`${json_args[i]}`, `${json_args[i]}`,
] ];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
xit('Valid command exit code = 1', () => { xit("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch( expect(result.output).toMatch(
/(No such file or directory|cannot find the file specified)/i /(No such file or directory|cannot find the file specified)/i,
) // Hopefully we should have more precise message here! ); // Hopefully we should have more precise message here!
}) });
xit('solc exit code == zksolc exit code', () => { xit("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
} }
//id1829:III //id1829:III
@@ -142,22 +146,22 @@ describe('Set of --combined-json tests', () => {
`${json_args[i]}`, `${json_args[i]}`,
`--combined-json`, `--combined-json`,
`${json_args[i]}`, `${json_args[i]}`,
] ];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
it('Valid command exit code = 1', () => { it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i) expect(result.output).toMatch(/(cannot be used multiple times)/i);
}) });
it('solc exit code == zksolc exit code', () => { it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
} }
//id1830 //id1830
@@ -167,22 +171,22 @@ describe('Set of --combined-json tests', () => {
`${paths.pathToBasicYulContract}`, `${paths.pathToBasicYulContract}`,
`--combined-json`, `--combined-json`,
`${json_args[i]}`, `${json_args[i]}`,
] ];
const result = executeCommand(zksolcCommand, args) const result = executeCommand(zksolcCommand, args);
it('Valid command exit code = 1', () => { it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1) expect(result.exitCode).toBe(1);
}) });
it('--combined-json error is presented', () => { it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i) expect(result.output).toMatch(/(ParserError: Expected identifier)/i);
}) });
asd asd;
it('solc exit code == zksolc exit code', () => { it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args) const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode) expect(solcResult.exitCode).toBe(result.exitCode);
}) });
}) });
} }
}) });
-1
View File
@@ -1 +0,0 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.wasm
-1
View File
@@ -1 +0,0 @@
../../../../target/wasm32-unknown-emscripten/release/resolc_web.js
-138
View File
@@ -1,138 +0,0 @@
import { expect } from 'chai'
import { compile } from '../examples/node/revive.js'
import { fileURLToPath } from 'url'
import path from 'path'
import fs from 'fs'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`)
return JSON.parse(fs.readFileSync(fixturePath, 'utf-8'))
}
describe('Compile Function Tests', function () {
it('should successfully compile valid Solidity code', async function () {
const standardInput = loadFixture('storage.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
expect(output.contracts['fixtures/storage.sol']).to.have.property('Storage')
expect(output.contracts['fixtures/storage.sol'].Storage).to.have.property(
'abi'
)
expect(output.contracts['fixtures/storage.sol'].Storage).to.have.property(
'evm'
)
expect(
output.contracts['fixtures/storage.sol'].Storage.evm
).to.have.property('bytecode')
})
if (typeof globalThis.Bun == 'undefined') {
// Running this test with Bun on a Linux host causes:
// RuntimeError: Out of bounds memory access (evaluating 'getWasmTableEntry(index)(a1, a2, a3, a4, a5)')
// Once this issue is resolved, the test will be re-enabled.
it('should successfully compile large Solidity code', async function () {
const standardInput = loadFixture('token.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
expect(output.contracts['fixtures/token.sol']).to.have.property('MyToken')
expect(output.contracts['fixtures/token.sol'].MyToken).to.have.property(
'abi'
)
expect(output.contracts['fixtures/token.sol'].MyToken).to.have.property(
'evm'
)
expect(
output.contracts['fixtures/token.sol'].MyToken.evm
).to.have.property('bytecode')
})
it('should successfully compile a valid Solidity contract that instantiates the token contracts', async function () {
const standardInput = loadFixture('instantiate_tokens.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
expect(
output.contracts['fixtures/instantiate_tokens.sol']
).to.have.property('TokensFactory')
expect(
output.contracts['fixtures/instantiate_tokens.sol'].TokensFactory
).to.have.property('abi')
expect(
output.contracts['fixtures/instantiate_tokens.sol'].TokensFactory
).to.have.property('evm')
expect(
output.contracts['fixtures/instantiate_tokens.sol'].TokensFactory.evm
).to.have.property('bytecode')
})
}
it('should throw an error for invalid Solidity code', async function () {
const standardInput = loadFixture('invalid_contract_content.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('errors')
expect(output.errors).to.be.an('array')
expect(output.errors.length).to.be.greaterThan(0)
expect(output.errors[0].type).to.exist
expect(output.errors[0].type).to.contain('ParserError')
})
it('should return not found error for missing imports', async function () {
const standardInput = loadFixture('missing_import.json')
const result = await compile(standardInput)
const output = JSON.parse(result)
expect(output).to.have.property('errors')
expect(output.errors).to.be.an('array')
expect(output.errors.length).to.be.greaterThan(0)
expect(output.errors[0].message).to.exist
expect(output.errors[0].message).to.include(
'Source "nonexistent/console.sol" not found'
)
})
it('should successfully compile a valid Solidity contract that instantiates another contract', async function () {
const standardInput = loadFixture('instantiate.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
expect(output.contracts['fixtures/instantiate.sol']).to.have.property(
'ChildContract'
)
expect(
output.contracts['fixtures/instantiate.sol'].ChildContract
).to.have.property('abi')
expect(
output.contracts['fixtures/instantiate.sol'].ChildContract
).to.have.property('evm')
expect(
output.contracts['fixtures/instantiate.sol'].ChildContract.evm
).to.have.property('bytecode')
expect(output.contracts['fixtures/instantiate.sol']).to.have.property(
'MainContract'
)
expect(
output.contracts['fixtures/instantiate.sol'].MainContract
).to.have.property('abi')
expect(
output.contracts['fixtures/instantiate.sol'].MainContract
).to.have.property('evm')
expect(
output.contracts['fixtures/instantiate.sol'].MainContract.evm
).to.have.property('bytecode')
})
})
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
+5 -2
View File
@@ -1,7 +1,10 @@
# @parity/revive
Node.js module to compile Solidity contracts to Polkavm bytecode, using [Revive](https://github.com/paritytech/revive)
# Usage from Node.js # Usage from Node.js
```typescript ```typescript
import { compile } from "@parity/resolc";
const sources = { const sources = {
["contracts/1_Storage.sol"]: { ["contracts/1_Storage.sol"]: {
content: readFileSync("fixtures/storage.sol", "utf8"), content: readFileSync("fixtures/storage.sol", "utf8"),
@@ -13,5 +16,5 @@ const out = await compile(sources);
# Usage from shell # Usage from shell
```bash ```bash
npx @parity/resolc@latest --bin contracts/1_Storage.sol npx @parity/revive@latest --bin contracts/1_Storage.sol
``` ```
@@ -4,8 +4,8 @@ import tseslint from 'typescript-eslint'
/** @type {import('eslint').Linter.Config[]} */ /** @type {import('eslint').Linter.Config[]} */
export default [ export default [
{ files: ['**/*.{mjs,ts}'] }, { files: ['**/*.{mjs,ts}'] },
{ languageOptions: { globals: globals.browser } }, { languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended, pluginJs.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
] ]
+7 -3
View File
@@ -24,14 +24,18 @@
"test": "npm run build && node ./dist/index.test.js" "test": "npm run build && node ./dist/index.test.js"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.14.0",
"@openzeppelin/contracts": "5.1.0", "@openzeppelin/contracts": "5.1.0",
"@types/node": "^22.14.1",
"eslint": "^9.14.0",
"globals": "^15.12.0", "globals": "^15.12.0",
"typescript": "^5.6.3" "typescript": "^5.6.3",
"typescript-eslint": "^8.13.0"
}, },
"dependencies": { "dependencies": {
"@types/node": "^22.9.0",
"commander": "^13.1.0", "commander": "^13.1.0",
"package-json": "^10.0.1", "package-json": "^10.0.1",
"solc": "^0.8.29" "solc": "^0.8.29"
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }
+172 -169
View File
@@ -8,193 +8,196 @@ import * as resolc from '.'
import { SolcInput } from '.' import { SolcInput } from '.'
async function main() { 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 // hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
const originalUncaughtExceptionListeners = const originalUncaughtExceptionListeners =
process.listeners('uncaughtException') process.listeners('uncaughtException')
// FIXME: remove annoying exception catcher of Emscripten // FIXME: remove annoying exception catcher of Emscripten
// see https://github.com/chriseth/browser-solidity/issues/167 // see https://github.com/chriseth/browser-solidity/issues/167
process.removeAllListeners('uncaughtException') process.removeAllListeners('uncaughtException')
const program = new commander.Command() const program = new commander.Command()
program.name('solcjs') program.name('solcjs')
program.version(resolc.version()) program.version(resolc.version())
program program
.option('--bin', 'Binary of the contracts in hex.') .option('--bin', 'Binary of the contracts in hex.')
.option('--abi', 'ABI of the contracts.') .option('--abi', 'ABI of the contracts.')
.option( .option(
'--base-path <path>', '--base-path <path>',
'Root of the project source tree. ' + 'Root of the project source tree. ' +
'The import callback will attempt to interpret all import paths as relative to this directory.' 'The import callback will attempt to interpret all import paths as relative to this directory.'
) )
.option( .option(
'--include-path <path...>', '--include-path <path...>',
'Extra source directories available to the import callback. ' + '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. ' + '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.' 'Can be used multiple times to provide multiple locations.'
) )
.option( .option(
'-o, --output-dir <output-directory>', '-o, --output-dir <output-directory>',
'Output directory for the contracts.' 'Output directory for the contracts.'
) )
.option('-p, --pretty-json', 'Pretty-print all JSON output.', false) .option('-p, --pretty-json', 'Pretty-print all JSON output.', false)
.option('-v, --verbose', 'More detailed console output.', false) .option('-v, --verbose', 'More detailed console output.', false)
.argument('<files...>') .argument('<files...>')
program.parse(process.argv) program.parse(process.argv)
const options = program.opts<{ const options = program.opts<{
verbose: boolean verbose: boolean
abi: boolean abi: boolean
bin: boolean bin: boolean
outputDir?: string outputDir?: string
prettyJson: boolean prettyJson: boolean
basePath?: string basePath?: string
includePath?: string[] includePath?: string[]
}>() }>()
const files: string[] = program.args const files: string[] = program.args
const destination = options.outputDir ?? '.' const destination = options.outputDir ?? '.'
function abort(msg: string) { function abort(msg: string) {
console.error(msg || 'Error occurred') console.error(msg || 'Error occurred')
process.exit(1) 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 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
}
function makeSourcePathRelativeIfPossible(sourcePath: string) { return filePath.replace(/\\/g, '/')
const absoluteBasePath = options.basePath }
? path.resolve(options.basePath)
: path.resolve('.') function makeSourcePathRelativeIfPossible(sourcePath: string) {
const absoluteIncludePaths = options.includePath const absoluteBasePath = options.basePath
? options.includePath.map((prefix: string) => { ? path.resolve(options.basePath)
return path.resolve(prefix) : 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) {
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)
}
}) })
: []
// 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. for (const fileName in output.contracts) {
return withUnixPathSeparators(absoluteSourcePath) for (const contractName in output.contracts[fileName]) {
} let contractFileName = fileName + ':' + contractName
contractFileName = contractFileName.replace(/[:./\\]/g, '_')
function toFormattedJson<T>(input: T) { if (options.bin) {
return JSON.stringify(input, null, options.prettyJson ? 4 : 0) writeFile(
} contractFileName + '.polkavm',
Buffer.from(
output.contracts[fileName][contractName].evm.bytecode
.object,
'hex'
)
)
}
if (files.length === 0) { if (options.abi) {
console.error('Must provide a file') writeFile(
process.exit(1) contractFileName + '.abi',
} toFormattedJson(
output.contracts[fileName][contractName].abi
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) { // Put back original exception handlers.
console.log('>>> Compiling:\n' + toFormattedJson(sources) + '\n') originalUncaughtExceptionListeners.forEach(function (listener) {
} process.addListener('uncaughtException', listener)
const output = await resolc.compile(sources)
let hasError = false
if (!output) {
abort('No output from compiler')
} else if (output.errors) {
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)
}
}) })
}
for (const fileName in output.contracts) { if (hasError) {
for (const contractName in output.contracts[fileName]) { process.exit(1)
let contractFileName = fileName + ':' + contractName
contractFileName = contractFileName.replace(/[:./\\]/g, '_')
if (options.bin) {
writeFile(
contractFileName + '.polkavm',
Buffer.from(
output.contracts[fileName][contractName].evm.bytecode.object,
'hex'
)
)
}
if (options.abi) {
writeFile(
contractFileName + '.abi',
toFormattedJson(output.contracts[fileName][contractName].abi)
)
}
} }
}
// Put back original exception handlers.
originalUncaughtExceptionListeners.forEach(function (listener) {
process.addListener('uncaughtException', listener)
})
if (hasError) {
process.exit(1)
}
} }
main().catch((err) => { main().catch((err) => {
console.error('Error:', err) console.error('Error:', err)
process.exit(1) process.exit(1)
}) })
+106 -90
View File
@@ -1,116 +1,132 @@
import { test } from 'node:test' import { test } from 'node:test'
import { readFileSync, existsSync } from 'node:fs' import { readFileSync } from 'node:fs'
import assert from 'node:assert' import assert from 'node:assert'
import { compile, tryResolveImport } from '.' import { compile, tryResolveImport } from '.'
import { resolve } from 'node:path' import { resolve } from 'node:path'
import { execSync } from 'node:child_process'
function isResolcInPath() {
try {
execSync('resolc --version', { stdio: 'ignore' })
return true
} catch {
return false
}
}
const compileOptions = [{}] const compileOptions = [{}]
if (existsSync('../../target/release/resolc')) { if (isResolcInPath()) {
compileOptions.push({ bin: '../../target/release/resolc' }) compileOptions.push({ bin: 'resolc' })
} }
for (const options of compileOptions) { for (const options of compileOptions) {
test(`check Ok output with option ${JSON.stringify(options)}`, async () => { test(`check Ok output with option ${JSON.stringify(options)}`, async () => {
const contract = 'fixtures/token.sol' const contract = 'fixtures/token.sol'
const sources = { const sources = {
[contract]: { [contract]: {
content: readFileSync('fixtures/storage.sol', 'utf8'), content: readFileSync('fixtures/storage.sol', 'utf8'),
}, },
} }
const out = await compile(sources, options) const out = await compile(sources, options)
assert(out.contracts[contract].Storage.abi != null) assert(out.contracts[contract].Storage.abi != null)
assert(out.contracts[contract].Storage.evm.bytecode != null) assert(out.contracts[contract].Storage.evm.bytecode != null)
}) })
} }
test('check Err output', async () => { test('check Err output', async () => {
const sources = { const sources = {
bad: { bad: {
content: readFileSync('fixtures/storage_bad.sol', 'utf8'), content: readFileSync('fixtures/storage_bad.sol', 'utf8'),
}, },
} }
const out = await compile(sources) const out = await compile(sources)
assert( assert(
out?.errors?.[0].message.includes( out?.errors?.[0].message.includes(
'SPDX license identifier not provided in source file' 'SPDX license identifier not provided in source file'
)
) )
) assert(
assert( out?.errors?.[1].message.includes(
out?.errors?.[1].message.includes( 'Source file does not specify required compiler version'
'Source file does not specify required compiler version' )
) )
)
}) })
test('check Err from stderr', async () => { test('check Err from stderr', async () => {
const sources = { const sources = {
bad: { bad: {
content: readFileSync('fixtures/bad_pragma.sol', 'utf8'), content: readFileSync('fixtures/bad_pragma.sol', 'utf8'),
}, },
} }
try { try {
await compile(sources) await compile(sources)
assert(false, 'Expected error') assert(false, 'Expected error')
} catch (error) { } catch (error) {
assert( assert(
String(error).includes('Source file requires different compiler version') String(error).includes(
) 'Source file requires different compiler version'
} )
)
}
}) })
test('resolve import', () => { test('resolve import', () => {
const cases = [ const cases = [
// local // local
{ {
file: './fixtures/storage.sol', file: './fixtures/storage.sol',
expected: resolve('fixtures/storage.sol'), expected: resolve('fixtures/storage.sol'),
}, },
// scopped module with version // scopped module with version
{ {
file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol', file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol',
expected: require.resolve( expected: resolve(
'@openzeppelin/contracts/token/ERC20/ERC20.sol' 'node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol'
), ),
}, },
// scopped module without version // scopped module without version
{ {
file: '@openzeppelin/contracts/token/ERC20/ERC20.sol', file: '@openzeppelin/contracts/token/ERC20/ERC20.sol',
expected: require.resolve( expected: resolve(
'@openzeppelin/contracts/token/ERC20/ERC20.sol' 'node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol'
), ),
}, },
// scopped module with wrong version // scopped module with wrong version
{ {
file: '@openzeppelin/contracts@4.8.3/token/ERC20/ERC20.sol', file: '@openzeppelin/contracts@4.8.3/token/ERC20/ERC20.sol',
expected: `Error: Version mismatch: Specified @openzeppelin/contracts@4.8.3, but installed version is 5.1.0`, expected: `Error: Version mismatch: Specified @openzeppelin/contracts@4.8.3, but installed version is 5.1.0`,
}, },
// module without version // module without version
{ {
file: '@openzeppelin/contracts/package.json', file: 'acorn/package.json',
expected: require.resolve('@openzeppelin/contracts/package.json'), expected: resolve('node_modules/acorn/package.json'),
}, },
// scopped module with version // scopped module with version
{ {
file: '@openzeppelin/contracts@5.1.0/package.json', file: 'acorn@8.14.0/package.json',
expected: require.resolve('@openzeppelin/contracts/package.json'), expected: resolve('node_modules/acorn/package.json'),
}, },
] {
file: 'acorn@8.14.1/package.json',
expected: `Error: Version mismatch: Specified acorn@8.14.1, but installed version is 8.14.0`,
},
]
for (const { file, expected } of cases) { for (const { file, expected } of cases) {
try { try {
const resolved = tryResolveImport(file) const resolved = tryResolveImport(file)
assert( assert(
resolved === expected, resolved === expected,
`\nGot:\n${resolved}\nExpected:\n${expected}` `\nGot:\n${resolved}\nExpected:\n${expected}`
) )
} catch (error) { } catch (error) {
assert( assert(
String(error) == expected, String(error) == expected,
`\nGot:\n${String(error)}\nExpected:\n${expected}` `\nGot:\n${String(error)}\nExpected:\n${expected}`
) )
}
} }
}
}) })
+153 -164
View File
@@ -5,127 +5,114 @@ import path from 'path'
import { existsSync, readFileSync } from 'fs' import { existsSync, readFileSync } from 'fs'
export type SolcInput = { export type SolcInput = {
[contractName: string]: { [contractName: string]: {
content: string content: string
} }
} }
export type SolcError = { export type SolcError = {
component: string component: string
errorCode: string errorCode: string
formattedMessage: string formattedMessage: string
message: string message: string
severity: string severity: string
sourceLocation?: { sourceLocation?: {
file: string file: string
start: number start: number
end: number end: number
} }
type: string type: string
} }
export type SolcOutput = { export type SolcOutput = {
contracts: { contracts: {
[contractPath: string]: { [contractPath: string]: {
[contractName: string]: { [contractName: string]: {
abi: Array<{ abi: Array<{
name: string name: string
inputs: Array<{ name: string; type: string }> inputs: Array<{ name: string; type: string }>
outputs: Array<{ name: string; type: string }> outputs: Array<{ name: string; type: string }>
stateMutability: string stateMutability: string
type: string type: string
}> }>
evm: { evm: {
bytecode: { object: string } bytecode: { object: string }
}
}
} }
}
} }
} errors?: Array<SolcError>
errors?: Array<SolcError>
} }
export function resolveInputs(sources: SolcInput): SolcInput { export function resolveInputs(sources: SolcInput): SolcInput {
const input = { const input = {
language: 'Solidity', language: 'Solidity',
sources, sources,
settings: { settings: {
outputSelection: { outputSelection: {
'*': { '*': {
'*': ['evm.bytecode.object'], '*': ['evm.bytecode.object'],
},
},
}, },
}, }
},
}
const out = solc.compile(JSON.stringify(input), { const out = solc.compile(JSON.stringify(input), {
import: (path: string) => { import: (path: string) => {
return { return {
contents: readFileSync(tryResolveImport(path), 'utf8'), contents: readFileSync(tryResolveImport(path), 'utf8'),
} }
},
})
const output = JSON.parse(out) as {
sources: { [fileName: string]: { id: number } }
errors: Array<SolcError>
}
if (output.errors && Object.keys(output.sources).length === 0) {
throw new Error(output.errors[0].formattedMessage)
}
return Object.fromEntries(
Object.keys(output.sources).map((fileName) => {
return [
fileName,
sources[fileName] ?? {
content: readFileSync(tryResolveImport(fileName), 'utf8'),
}, },
]
}) })
)
const output = JSON.parse(out) as {
sources: { [fileName: string]: { id: number } }
errors: Array<SolcError>
}
if (output.errors && Object.keys(output.sources).length === 0) {
throw new Error(output.errors[0].formattedMessage)
}
return Object.fromEntries(
Object.keys(output.sources).map((fileName) => {
return [
fileName,
sources[fileName] ?? {
content: readFileSync(tryResolveImport(fileName), 'utf8'),
},
]
})
)
} }
export function version(): string { export function version(): string {
const v = resolcVersion() const v = resolcVersion()
return v.split(' ').pop() ?? v return v.split(' ').pop() ?? v
} }
export async function compile( export async function compile(
sources: SolcInput, sources: SolcInput,
option: { option: { bin?: string } = {}
optimizer?: Record<string, unknown>
bin?: string
} = {}
): Promise<SolcOutput> { ): Promise<SolcOutput> {
const { const input = JSON.stringify({
optimizer = { language: 'Solidity',
mode: 'z', sources: resolveInputs(sources),
fallback_to_optimizing_for_size: true, settings: {
enabled: true, optimizer: { enabled: true, runs: 200 },
runs: 200, outputSelection: {
}, '*': {
bin, '*': ['abi'],
} = option },
},
const input = JSON.stringify({
language: 'Solidity',
sources: resolveInputs(sources),
settings: {
optimizer,
outputSelection: {
'*': {
'*': ['abi'],
}, },
}, })
},
})
if (bin) { if (option.bin) {
return compileWithBin(input, bin) return compileWithBin(input, option.bin)
} }
return resolc(input) return resolc(input)
} }
/** /**
@@ -133,81 +120,83 @@ export async function compile(
* @param importPath - The import path to resolve. * @param importPath - The import path to resolve.
*/ */
export function tryResolveImport(importPath: string) { export function tryResolveImport(importPath: string) {
// resolve local path // resolve local path
if (existsSync(importPath)) { if (existsSync(importPath)) {
return path.resolve(importPath) return path.resolve(importPath)
}
const importRegex = /^(@?[^@/]+(?:\/[^@/]+)?)(?:@([^/]+))?(\/.+)$/
const match = importPath.match(importRegex)
if (!match) {
throw new Error('Invalid import path format.')
}
const basePackage = match[1] // "foo", "@scope/foo"
const specifiedVersion = match[2] // "1.2.3" (optional)
const relativePath = match[3] // "/path/to/file.sol"
let packageJsonPath
try {
packageJsonPath = require.resolve(path.join(basePackage, 'package.json'))
} catch {
throw new Error(`Could not resolve package ${basePackage}`)
}
// Check if a version was specified and compare with the installed version
if (specifiedVersion) {
const installedVersion = JSON.parse(
readFileSync(packageJsonPath, 'utf-8')
).version
if (installedVersion !== specifiedVersion) {
throw new Error(
`Version mismatch: Specified ${basePackage}@${specifiedVersion}, but installed version is ${installedVersion}`
)
} }
}
const packageRoot = path.dirname(packageJsonPath) const importRegex = /^(@?[^@/]+(?:\/[^@/]+)?)(?:@([^/]+))?(\/.+)$/
const match = importPath.match(importRegex)
// Construct full path to the requested file if (!match) {
const resolvedPath = path.join(packageRoot, relativePath) throw new Error('Invalid import path format.')
if (existsSync(resolvedPath)) { }
return resolvedPath
} else { const basePackage = match[1] // "foo", "@scope/foo"
throw new Error(`Resolved path ${resolvedPath} does not exist.`) const specifiedVersion = match[2] // "1.2.3" (optional)
} const relativePath = match[3] // "/path/to/file.sol"
let packageJsonPath
try {
packageJsonPath = require.resolve(
path.join(basePackage, 'package.json')
)
} catch {
throw new Error(`Could not resolve package ${basePackage}`)
}
// Check if a version was specified and compare with the installed version
if (specifiedVersion) {
const installedVersion = JSON.parse(
readFileSync(packageJsonPath, 'utf-8')
).version
if (installedVersion !== specifiedVersion) {
throw new Error(
`Version mismatch: Specified ${basePackage}@${specifiedVersion}, but installed version is ${installedVersion}`
)
}
}
const packageRoot = path.dirname(packageJsonPath)
// Construct full path to the requested file
const resolvedPath = path.join(packageRoot, relativePath)
if (existsSync(resolvedPath)) {
return resolvedPath
} else {
throw new Error(`Resolved path ${resolvedPath} does not exist.`)
}
} }
function compileWithBin(input: string, bin: string): PromiseLike<SolcOutput> { function compileWithBin(input: string, bin: string): PromiseLike<SolcOutput> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const process = spawn(bin, ['--standard-json']) const process = spawn(bin, ['--standard-json'])
let output = '' let output = ''
let error = '' let error = ''
process.stdin.write(input) process.stdin.write(input)
process.stdin.end() process.stdin.end()
process.stdout.on('data', (data) => { process.stdout.on('data', (data) => {
output += data.toString() output += data.toString()
})
process.stderr.on('data', (data) => {
error += data.toString()
})
process.on('close', (code) => {
if (code === 0) {
try {
const result: SolcOutput = JSON.parse(output)
resolve(result)
} catch {
reject(new Error(`Failed to parse output`))
}
} else {
reject(new Error(`Process exited with code ${code}: ${error}`))
}
})
}) })
process.stderr.on('data', (data) => {
error += data.toString()
})
process.on('close', (code) => {
if (code === 0) {
try {
const result: SolcOutput = JSON.parse(output)
resolve(result)
} catch {
reject(new Error(`Failed to parse output`))
}
} else {
reject(new Error(`Process exited with code ${code}: ${error}`))
}
})
})
} }
+17 -17
View File
@@ -3,28 +3,28 @@ import Resolc from './resolc/resolc'
import type { SolcOutput } from '.' import type { SolcOutput } from '.'
export function resolc(input: string): SolcOutput { export function resolc(input: string): SolcOutput {
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson m.soljson = soljson
m.writeToStdin(input) m.writeToStdin(input)
m.callMain(['--standard-json']) m.callMain(['--standard-json'])
const err = m.readFromStderr() const err = m.readFromStderr()
if (err) { if (err) {
throw new Error(err) throw new Error(err)
} }
return JSON.parse(m.readFromStdout()) as SolcOutput return JSON.parse(m.readFromStdout()) as SolcOutput
} }
export function version(): string { export function version(): string {
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson m.soljson = soljson
m.callMain(['--version']) m.callMain(['--version'])
const err = m.readFromStderr() const err = m.readFromStderr()
if (err) { if (err) {
throw new Error(err) throw new Error(err)
} }
return m.readFromStdout() return m.readFromStdout()
} }
+86 -86
View File
@@ -1,95 +1,95 @@
declare module 'solc/soljson' {} declare module 'solc/soljson' {}
declare module 'solc' { declare module 'solc' {
// Basic types for input/output handling // Basic types for input/output handling
export interface CompileInput { export interface CompileInput {
language: string language: string
sources: { sources: {
[fileName: string]: { [fileName: string]: {
content: string content: string
} }
} }
settings?: { settings?: {
optimizer?: { optimizer?: {
enabled: boolean enabled: boolean
runs: number runs: number
} }
outputSelection: { outputSelection: {
[fileName: string]: { [fileName: string]: {
[contractName: string]: string[] [contractName: string]: string[]
} }
}
}
}
export interface CompileOutput {
errors?: Array<{
component: string
errorCode: string
formattedMessage: string
message: string
severity: string
sourceLocation?: {
file: string
start: number
end: number
}
type: string
}>
sources?: {
[fileName: string]: {
id: number
ast: object
}
}
contracts?: {
[fileName: string]: {
[contractName: string]: {
abi: object[]
evm: {
bytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
} }
deployedBytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
}
} }
}
} }
}
// Main exported functions export interface CompileOutput {
export function compile( errors?: Array<{
input: string | CompileInput, component: string
options?: { errorCode: string
import: (path: string) => formattedMessage: string
| { message: string
contents: string severity: string
error?: undefined sourceLocation?: {
} file: string
| { start: number
error: string end: number
contents?: undefined }
} type: string
}>
sources?: {
[fileName: string]: {
id: number
ast: object
}
}
contracts?: {
[fileName: string]: {
[contractName: string]: {
abi: object[]
evm: {
bytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
deployedBytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
}
}
}
}
} }
): string
// Main exported functions
export function compile(
input: string | CompileInput,
options?: {
import: (path: string) =>
| {
contents: string
error?: undefined
}
| {
error: string
contents?: undefined
}
}
): string
} }
+1 -1
View File
@@ -17,7 +17,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"useUnknownInCatchVariables": true, "useUnknownInCatchVariables": true,
"types": ["node"], "types": ["node"],
"typeRoots": ["../../node_modules/@types", "./src"], "typeRoots": ["./node_modules/@types", "./src"],
"paths": { "paths": {
"#src/*": ["./src/*"] "#src/*": ["./src/*"]
}, },
+1
View File
@@ -0,0 +1 @@
../../../target/wasm32-unknown-emscripten/release/resolc.wasm
+1
View File
@@ -0,0 +1 @@
../../../target/wasm32-unknown-emscripten/release/resolc_web.js
+142
View File
@@ -0,0 +1,142 @@
import { expect } from "chai";
import { compile } from "../examples/node/revive.js";
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`);
return JSON.parse(fs.readFileSync(fixturePath, "utf-8"));
}
describe("Compile Function Tests", function () {
it("should successfully compile valid Solidity code", async function () {
const standardInput = loadFixture("storage.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(output.contracts["fixtures/storage.sol"]).to.have.property(
"Storage",
);
expect(output.contracts["fixtures/storage.sol"].Storage).to.have.property(
"abi",
);
expect(output.contracts["fixtures/storage.sol"].Storage).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/storage.sol"].Storage.evm,
).to.have.property("bytecode");
});
if (typeof globalThis.Bun == "undefined") {
// Running this test with Bun on a Linux host causes:
// RuntimeError: Out of bounds memory access (evaluating 'getWasmTableEntry(index)(a1, a2, a3, a4, a5)')
// Once this issue is resolved, the test will be re-enabled.
it("should successfully compile large Solidity code", async function () {
const standardInput = loadFixture("token.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(output.contracts["fixtures/token.sol"]).to.have.property(
"MyToken",
);
expect(output.contracts["fixtures/token.sol"].MyToken).to.have.property(
"abi",
);
expect(output.contracts["fixtures/token.sol"].MyToken).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/token.sol"].MyToken.evm,
).to.have.property("bytecode");
});
it("should successfully compile a valid Solidity contract that instantiates the token contracts", async function () {
const standardInput = loadFixture("instantiate_tokens.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(
output.contracts["fixtures/instantiate_tokens.sol"],
).to.have.property("TokensFactory");
expect(
output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory,
).to.have.property("abi");
expect(
output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory,
).to.have.property("evm");
expect(
output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory.evm,
).to.have.property("bytecode");
});
}
it("should throw an error for invalid Solidity code", async function () {
const standardInput = loadFixture("invalid_contract_content.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("errors");
expect(output.errors).to.be.an("array");
expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].type).to.exist;
expect(output.errors[0].type).to.contain("ParserError");
});
it("should return not found error for missing imports", async function () {
const standardInput = loadFixture("missing_import.json");
const result = await compile(standardInput);
const output = JSON.parse(result);
expect(output).to.have.property("errors");
expect(output.errors).to.be.an("array");
expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].message).to.exist;
expect(output.errors[0].message).to.include(
'Source "nonexistent/console.sol" not found',
);
});
it("should successfully compile a valid Solidity contract that instantiates another contract", async function () {
const standardInput = loadFixture("instantiate.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(output.contracts["fixtures/instantiate.sol"]).to.have.property(
"ChildContract",
);
expect(
output.contracts["fixtures/instantiate.sol"].ChildContract,
).to.have.property("abi");
expect(
output.contracts["fixtures/instantiate.sol"].ChildContract,
).to.have.property("evm");
expect(
output.contracts["fixtures/instantiate.sol"].ChildContract.evm,
).to.have.property("bytecode");
expect(output.contracts["fixtures/instantiate.sol"]).to.have.property(
"MainContract",
);
expect(
output.contracts["fixtures/instantiate.sol"].MainContract,
).to.have.property("abi");
expect(
output.contracts["fixtures/instantiate.sol"].MainContract,
).to.have.property("evm");
expect(
output.contracts["fixtures/instantiate.sol"].MainContract.evm,
).to.have.property("bytecode");
});
});
-6618
View File
File diff suppressed because it is too large Load Diff
+6 -11
View File
@@ -3,19 +3,14 @@
"private": true, "private": true,
"scripts": { "scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests", "test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"test:wasm": "npm run test:node -w js/emscripten", "test:wasm": "npm run test:node -w js/wasm",
"build:package": "npm run build:package -w js/emscripten", "build:package": "npm run build:package -w js/wasm",
"lint": "npx eslint 'js/**/*.{cjs,mjs,ts}' && npx prettier --check '**/*.{mjs,cjs,ts}'", "lint": "npx eslint 'js/**/*.{cjs,mjs,ts}' && npx prettier --check '**/*.{ts,json,yml,md}'",
"lint:fix": "npx prettier --write '**/*.{mjs,cjs,ts}'" "lint:fix": "npx prettier --write '**/*.{mjs,cjs,ts,json,yml,md}'"
}, },
"workspaces": [ "workspaces": [
"crates/solidity/src/tests/cli-tests", "crates/solidity/src/tests/cli-tests",
"js/emscripten", "js/wasm",
"js/resolc" "js/resolc"
], ]
"dependencies": {
"@eslint/js": "^9.14.0",
"eslint": "^9.14.0",
"typescript-eslint": "^8.13.0"
}
} }
+3635
View File
File diff suppressed because it is too large Load Diff