Compare commits

..

15 Commits

Author SHA1 Message Date
Cyrill Leutwiler 9c83352fdb update emsdk
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-20 21:04:58 +02:00
xermicus bb2f829361 expose custom PVM settings in the standard json interface (#318)
Exposes the following PolkaVM specific options via the standard json
interface:
- Heap size
- Stack size
- Whether to emit source level debug information

Additionally it is now forbidden to specify those as CLI option in
standard JSON mode. They are bytecode altering options and having
multiple ways to specify them creates unnecessary room for confusion:
The standard JSON input description should be sufficient and succint for
reproducible builds.

Closes #290

---------

Signed-off-by: xermicus <bigcyrill@hotmail.com>
2025-05-13 15:19:00 +02:00
xermicus 1b8fcc4649 Update the Rust version (#316)
- Update the Rust version to 1.85
- Update the package-lock.json
- Update the musl-cross docker image

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-09 20:00:58 +02:00
Cyrill Leutwiler 0e9e405f21 remove any git dependencies
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-09 17:26:20 +02:00
xermicus 722dd86c27 remove STATUS.md (#315)
This information is provided in the docs.

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-09 12:21:25 +02:00
PG Herveou 0421869e4b Replace release 0.1.0-dev.15 with 0.1.0-dev.16 (#314)
add missing patch and rename release
2025-05-08 15:26:10 +02:00
xermicus 32f55b976c update changelog (#313)
somehow went wrong

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-08 14:16:46 +02:00
PG Herveou 459a786299 v0.1.0-dev.15 release (#311)
Co-authored-by: xermicus <cyrill@parity.io>
2025-05-08 14:09:55 +02:00
xermicus fbaa45f283 support solc v0.8.30 (#308)
- Add support for solc `v0.8.30`. No changes in YUL or the binary
interface.
- Fix a semver bug in the NPM packages to correctly align their solc
dependencies with our last supported version.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-08 13:29:13 +02:00
xermicus f2fac85dae add npm package information to release checklist (#312)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-08 13:14:28 +02:00
PG Herveou b8f3073e29 Run lint:fix (#309) 2025-05-08 12:36:11 +02:00
xermicus 11d47d74ac apply size optimizations by default (#298)
So far if no optimization level was specified, optimizations for
execution time were applied. However, we currently are a bit limited on
code size. Add to that, this setting is not available in solc and people
generally ignore the docs, generating a lot of support requests.

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-07 15:35:48 +02:00
xermicus e3a9c95d32 llvm-builder: use the ninja generator for building the builtins on windows (#299)
The builtins build should use the Ninja generator (MSVC does not build a
valid archive).

Tested and verified here:
https://github.com/paritytech/revive-alex-workflowtest/releases/tag/untagged-f02d0f574bab8404fead

Closes #305

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-07 11:10:58 +02:00
PG Herveou a560b2d919 Fix npm-release job (#297)
follow up from #295
2025-04-30 21:48:11 +02:00
PG Herveou e07d0f0cb7 Move @parity/resolc from js-revive (#296)
- Move npm package from paritytech/js-revive 
- Rename package to `@parity/resolc`
2025-04-30 17:24:52 +02:00
94 changed files with 10064 additions and 7322 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ rustflags = [
"-Clink-arg=-sWASM_ASYNC_COMPILATION=0",
"-Clink-arg=-sDYNAMIC_EXECUTION=0",
"-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=--js-library=js/emscripten/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/emscripten/embed/pre.js",
"-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0"
]
+19 -18
View File
@@ -1,18 +1,19 @@
name: "Get Emscripten SDK"
inputs:
version:
description: ""
required: false
default: "3.1.64"
runs:
using: "composite"
steps:
- name: install emsdk
shell: bash
run: |
git clone https://github.com/emscripten-core/emsdk.git ./emsdk/
cd emsdk
git checkout tags/${{ inputs.version }}
./emsdk install ${{ inputs.version }}
./emsdk activate ${{ inputs.version }}
name: "Get Emscripten SDK"
inputs:
version:
description: ""
required: false
default: "4.0.9"
runs:
using: "composite"
steps:
- name: install emsdk
shell: bash
run: |
git clone https://github.com/emscripten-core/emsdk.git ./emsdk/
cd emsdk
git checkout tags/${{ inputs.version }}
./emsdk install ${{ inputs.version }}
./emsdk activate ${{ inputs.version }}
+70 -70
View File
@@ -1,70 +1,70 @@
# example:
# - uses: ./.github/actions/get-llvm
# with:
# target: x86_64-unknown-linux-gnu
name: "Download LLVM"
inputs:
target:
required: true
runs:
using: "composite"
steps:
- name: find asset
id: find
uses: actions/github-script@v7
env:
target: ${{ inputs.target }}
with:
result-encoding: string
script: |
let page = 1;
let releases = [];
let releasePrefix = "llvm-"
let target = process.env.target
do {
const res = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 50,
page,
});
releases = res.data
releases.sort((a, b) => {
return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0);
});
let llvmLatestRelease = releases.find(release => {
return release.tag_name.startsWith(releasePrefix);
});
if (llvmLatestRelease){
let asset = llvmLatestRelease.assets.find(asset =>{
return asset.name.includes(target);
});
if (!asset){
core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`);
process.exit();
}
return asset.browser_download_url;
}
page++;
} while(releases.length > 0);
core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`);
process.exit();
- name: download
shell: bash
run: |
curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }}
- name: unpack
shell: bash
run: |
tar -xf llvm.tar.gz
rm llvm.tar.gz
# example:
# - uses: ./.github/actions/get-llvm
# with:
# target: x86_64-unknown-linux-gnu
name: "Download LLVM"
inputs:
target:
required: true
runs:
using: "composite"
steps:
- name: find asset
id: find
uses: actions/github-script@v7
env:
target: ${{ inputs.target }}
with:
result-encoding: string
script: |
let page = 1;
let releases = [];
let releasePrefix = "llvm-"
let target = process.env.target
do {
const res = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 50,
page,
});
releases = res.data
releases.sort((a, b) => {
return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0);
});
let llvmLatestRelease = releases.find(release => {
return release.tag_name.startsWith(releasePrefix);
});
if (llvmLatestRelease){
let asset = llvmLatestRelease.assets.find(asset =>{
return asset.name.includes(target);
});
if (!asset){
core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`);
process.exit();
}
return asset.browser_download_url;
}
page++;
} while(releases.length > 0);
core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`);
process.exit();
- name: download
shell: bash
run: |
curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }}
- name: unpack
shell: bash
run: |
tar -xf llvm.tar.gz
rm llvm.tar.gz
+1 -1
View File
@@ -19,7 +19,7 @@ runs:
shell: bash
run: |
mkdir -p solc
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.29/${SOLC_NAME}
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.30/${SOLC_NAME}
- name: Make Solc Executable
if: ${{ runner.os == 'Windows' }}
-49
View File
@@ -1,49 +0,0 @@
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 }}
+6 -9
View File
@@ -37,15 +37,7 @@ jobs:
build:
strategy:
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:
- target: x86_64-unknown-linux-gnu
builder-arg: gnu
@@ -95,6 +87,11 @@ jobs:
run: |
brew install ninja
- name: Install Dependencies
if: ${{ matrix.host == 'windows' }}
run: |
choco install ninja
- name: Install LLVM Builder
run: |
cargo install --path crates/llvm-builder
+42 -2
View File
@@ -14,7 +14,7 @@ concurrency:
env:
CARGO_TERM_COLOR: always
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:68b86bc7cb2867259e6b233415a665ff4469c28b57763e78c3bfea1c68091561
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:c0154e992adb791c3b848dd008939d19862549204f8cb26f5ca7a00f629e6067
jobs:
check-version-changed:
@@ -181,7 +181,7 @@ jobs:
- name: Basic Sanity Check
run: |
mkdir -p solc
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.29/soljson.js
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.30/soljson.js
node -e "
const soljson = require('solc/soljson');
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
@@ -286,3 +286,43 @@ jobs:
resolc.wasm
resolc_web.js
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: 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"]
types: [opened, synchronize]
paths:
- "LLVM.lock"
- "crates/llvm-builder/**"
- ".github/workflows/test-llvm-builder.yml"
- 'LLVM.lock'
- 'crates/llvm-builder/**'
- '.github/workflows/test-llvm-builder.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+12 -7
View File
@@ -86,13 +86,18 @@ jobs:
- name: Install Node Packages
run: npm install
- name: Run Playwright tests
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
- name: Test emscripten
run: |
echo "Running tests for ${{ matrix.os }}"
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,7 +12,6 @@ target-llvm
node_modules
artifacts
tmp
package-lock.json
/*.html
/build
soljson.js
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
+31
View File
@@ -4,14 +4,45 @@
This is a development pre-release.
Supported `polkadot-sdk` rev: `2503.0.1`
## v0.1.0
This is a development pre-release.
Supported `polkadot-sdk` rev: `2503.0.1`
### Added
- Add the PolkaVM heap size, stack size and debug info CLI compiler options to the standard JSON settings. This makes the standard JSON input succint for reproducible builds.
### Changed
- Supported `polkadot-sdk` version is now `2503.0.1`
- The `emsdk` version is now `4.0.9`
### Fixed
## v0.1.0-dev.16
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
- Move the npm package from paritytech/js-revive, into this repo. The package `@parity/resolc` will be deployed to npm for each release.
- Support for solc v0.8.30
### Changed
- By default, heavy size optimizations are applied.
### Fixed
- @parity/resolc: The solc dependency package is constrained to the latest supported version, preventing breaking the package ever time a new solc package was released.
- The resolc npm package no longer ignores the optimizer settings
## v0.1.0-dev.14
This is a development pre-release.
Generated
+1813 -2091
View File
File diff suppressed because it is too large Load Diff
+20 -21
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.1.0-dev.14"
version = "0.1.0-dev.16"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>",
@@ -11,27 +11,27 @@ authors = [
license = "MIT/Apache-2.0"
edition = "2021"
repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0"
rust-version = "1.85.0"
[workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.14", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.14", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.14", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.14", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.14", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.14", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.14", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.14", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.14", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.14", path = "crates/runner" }
revive-solc-json-interface = { version = "0.1.0-dev.14", path = "crates/solc-json-interface" }
revive-solidity = { version = "0.1.0-dev.14", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.14", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.14", path = "crates/build-utils" }
revive-benchmarks = { version = "0.1.0-dev.16", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.16", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.16", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.16", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.16", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.16", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.16", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.16", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.16", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.16", path = "crates/runner" }
revive-solc-json-interface = { version = "0.1.0-dev.16", path = "crates/solc-json-interface" }
revive-solidity = { version = "0.1.0-dev.16", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.16", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.16", path = "crates/build-utils" }
hex = "0.4.3"
cc = "1.2"
libc = "0.2.169"
libc = "0.2.172"
tempfile = "3.17"
anyhow = "1.0"
semver = { version = "1.0", features = ["serde"] }
@@ -73,14 +73,13 @@ assert_fs = "1.1"
# polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "c29e72a8628835e34deb6aa7db9a78a2e4eabcee" }
polkadot-sdk = { version = "2503.0.1" }
# llvm
[workspace.dependencies.inkwell]
git = "https://github.com/TheDan64/inkwell.git"
rev = "7b410298b6a93450adaa90b1841d5805a3038f12"
version = "0.6.0"
default-features = false
features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"]
features = ["serde", "llvm18-1", "no-libffi-linking", "target-riscv"]
[profile.bench]
inherits = "release"
+2 -2
View File
@@ -1,4 +1,4 @@
FROM rust:1.84.0 AS llvm-builder
FROM rust:1.85.0 AS llvm-builder
WORKDIR /opt/revive
RUN apt update && \
@@ -11,7 +11,7 @@ RUN make install-llvm-builder
RUN revive-llvm --target-env musl clone
RUN revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang
FROM messense/rust-musl-cross@sha256:68b86bc7cb2867259e6b233415a665ff4469c28b57763e78c3bfea1c68091561 AS resolc-builder
FROM messense/rust-musl-cross@sha256:c0154e992adb791c3b848dd008939d19862549204f8cb26f5ca7a00f629e6067 AS resolc-builder
WORKDIR /opt/revive
RUN apt update && \
+1 -4
View File
@@ -92,7 +92,4 @@ clean:
rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \
cargo uninstall revive-llvm-builder ; \
rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
cargo uninstall revive-llvm-builder ;
+3 -7
View File
@@ -14,7 +14,6 @@ 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).
## Installation
Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions.
## Building from source
@@ -31,13 +30,11 @@ 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.
> **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive:
>
> ```sh
> xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/*
> ```
After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it:
```sh
export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final
```
@@ -47,7 +44,7 @@ export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/targ
<details>
<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:
@@ -96,12 +93,11 @@ make test-wasm
## 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.
### 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).
+5 -1
View File
@@ -13,9 +13,13 @@ To create a new pre-release:
7. After the release is published, another workflow should start automatically and update json files in https://github.com/paritytech/resolc-bin. Check the changes.
8. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
# `resolc` NPM package release
Will happen automatically.
# LLVM release
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.
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
-30
View File
@@ -1,30 +0,0 @@
# Known issues
The following is known and we are either working on it or it is a hard limitation. Please do not open a new issue.
## Release
`0.1.0-dev-2`
## Missing features
- [Libraries with public functions are not supported](https://github.com/paritytech/revive/issues/91)
- [Automatic import resolution is not supported](https://github.com/paritytech/revive/issues/98)
- The emulated EVM linear contract memory is limited to 64kb in size. Will be fixed with support for metered dynamic memory.
- [The contract calldata is currently limited to 1kb in size](https://github.com/paritytech/revive/issues/57)
- [EIP-4844 opcodes are not supported](https://github.com/paritytech/revive/issues/64)
- [Delegate calls are not supported](https://github.com/paritytech/revive/issues/67)
- [The `blockhash` opcode is not supported](https://github.com/paritytech/revive/issues/61)
- [The `extcodesize` opcode is not supported](https://github.com/paritytech/revive/issues/58)
- [The `origin` opcode is not supported](https://github.com/paritytech/revive/issues/59)
- [Gas limits for contract calls are ignored](https://github.com/paritytech/revive/issues/60)
- [Gas related opcodes are not supported](https://github.com/paritytech/revive/issues/60)
- IPFS metadata hashes are not supported
- [Compiled contract artifacts can exceed the pallet static memory limit and fail to deploy](https://github.com/paritytech/revive/issues/96).
- [Transfers to inexistant accounts will fail if the transferred value lies below the ED.](https://github.com/paritytech/revive/issues/83) Will be fixed in the pallet to make the ED completely transparent for contracts.
## Wontfix
Please consult our documentation to learn more about Solidity and EVM features likely to remain unsupported (and why they will not be supported).
TODO: Insert link to the relevant documentation section.
+42 -42
View File
@@ -3,71 +3,71 @@
## Table of Contents
- [Benchmark Results](#benchmark-results)
- [Baseline](#baseline)
- [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1)
- [Baseline](#baseline)
- [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1)
## Benchmark Results
### Baseline
| | `EVM` | `PVMInterpreter` |
| :------ | :------------------------ | :------------------------------- |
| **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) |
| | `EVM` | `PVMInterpreter` |
|:--------|:-------------------------|:-------------------------------- |
| **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) |
### OddPorduct
| | `EVM` | `PVMInterpreter` |
| :----------- | :------------------------- | :------------------------------- |
| **`10000`** | `3.60 ms` (✅ **1.00x**) | `1.57 ms` (🚀 **2.28x 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**) |
| | `EVM` | `PVMInterpreter` |
|:-------------|:--------------------------|:-------------------------------- |
| **`10000`** | `3.60 ms` (✅ **1.00x**) | `1.57 ms` (🚀 **2.28x 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**) |
### TriangleNumber
| | `EVM` | `PVMInterpreter` |
| :----------- | :------------------------ | :------------------------------- |
| **`10000`** | `2.43 ms` (✅ **1.00x**) | `1.12 ms` (🚀 **2.17x 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**) |
| | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- |
| **`10000`** | `2.43 ms` (✅ **1.00x**) | `1.12 ms` (🚀 **2.17x 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**) |
### FibonacciRecursive
| | `EVM` | `PVMInterpreter` |
| :------- | :------------------------- | :-------------------------------- |
| **`12`** | `144.17 us` (✅ **1.00x**) | `150.85 us` (✅ **1.05x slower**) |
| **`16`** | `938.71 us` (✅ **1.00x**) | `922.11 us` (✅ **1.02x 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**) |
| | `EVM` | `PVMInterpreter` |
|:---------|:--------------------------|:--------------------------------- |
| **`12`** | `144.17 us` (✅ **1.00x**) | `150.85 us` (✅ **1.05x slower**) |
| **`16`** | `938.71 us` (✅ **1.00x**) | `922.11 us` (✅ **1.02x 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**) |
### FibonacciIterative
| | `EVM` | `PVMInterpreter` |
| :-------- | :------------------------ | :------------------------------- |
| **`64`** | `23.00 us` (✅ **1.00x**) | `31.88 us` (❌ _1.39x 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**) |
| | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- |
| **`64`** | `23.00 us` (✅ **1.00x**) | `31.88 us` (❌ *1.39x 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**) |
### FibonacciBinet
| | `EVM` | `PVMInterpreter` |
| :-------- | :------------------------ | :----------------------------- |
| **`64`** | `23.01 us` (✅ **1.00x**) | `47.74 us` (❌ _2.07x 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_) |
| | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- |
| **`64`** | `23.01 us` (✅ **1.00x**) | `47.74 us` (❌ *2.07x 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*) |
### SHA1
| | `EVM` | `PVMInterpreter` |
| :-------- | :------------------------- | :------------------------------ |
| **`1`** | `135.87 us` (✅ **1.00x**) | `243.75 us` (❌ _1.79x 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**) |
| | `EVM` | `PVMInterpreter` |
|:----------|:--------------------------|:--------------------------------- |
| **`1`** | `135.87 us` (✅ **1.00x**) | `243.75 us` (❌ *1.79x 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**) |
---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+44 -44
View File
@@ -1,47 +1,47 @@
{
"config": {
"chainId": 420420420,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
},
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
"config": {
"chainId": 420420420,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
},
"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,
"Flipper": 2099,
"SHA1": 8243
}
}
+55 -64
View File
@@ -6,7 +6,6 @@ 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`.
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
- The RISC-V target (the PolkaVM target)
- The compiler-rt builtins for the PolkaVM target
@@ -18,92 +17,85 @@ Obtain a compatible build for your host platform from the release section of thi
<details>
<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):
```shell
apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
```
Install the following packages:
```shell
pacman -Syu which cmake ninja curl git pkg-config clang lld
```
- Linux (Arch):
* MacOS:
Install the following packages:
* Install the [HomeBrew](https://brew.sh) package manager.
* Install the following packages:
```shell
pacman -Syu which cmake ninja curl git pkg-config clang lld
```
```shell
brew install cmake ninja coreutils
```
- MacOS:
* 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>
* 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>
<summary>2. Install Rust.</summary>
- Follow the latest [official instructions](https://www.rust-lang.org/tools/install:
`shell
* Follow the latest [official instructions](https://www.rust-lang.org/tools/install:
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. ${HOME}/.cargo/env
`
```
> Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform.
</details>
> Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform.
</details>
<details>
<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
```
```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`.
> 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>
<summary>4. (Optional) Create the `LLVM.lock` file.</summary>
- The `LLVM.lock` dictates the LLVM source tree being used.
A default `./LLVM.lock` pointing to the release used for development is already provided.
* The `LLVM.lock` dictates the LLVM source tree being used.
A default `./LLVM.lock` pointing to the release used for development is already provided.
</details>
<details>
<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
revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang
```
```shell
revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang
```
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`.
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`.
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`.
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`.
</details>
## Supported target architectures
The following target platforms are supported:
- Linux GNU (x86)
- Linux MUSL (x86)
- MacOS (aarch64)
@@ -113,29 +105,28 @@ The following target platforms are supported:
<details>
<summary>Building for MUSL</summary>
- Via a musl build we can build revive into fully static ELF binaries.
Which is desirable for reproducible Solidity contracts builds.
The resulting binary is also very portable, akin to the`solc` frontend binary distribution.
* Via a musl build we can build revive into fully static ELF binaries.
Which is desirable for reproducible Solidity contracts builds.
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:
```shell
revive-llvm --target-env musl clone
revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld
```
Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env musl clone
revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details>
<details>
<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:
```shell
revive-llvm --target-env emscripten clone
revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld
```
Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env emscripten clone
revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details>
+2 -8
View File
@@ -117,13 +117,7 @@ pub fn build(
log::info!("building compiler-rt for rv64emac");
crate::utils::check_presence("cmake")?;
let generator = if cfg!(target_os = "windows") {
"Visual Studio 17 2022"
} else {
crate::utils::check_presence("ninja")?;
"Ninja"
};
crate::utils::check_presence("ninja")?;
let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?;
let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?;
@@ -136,7 +130,7 @@ pub fn build(
"-B",
llvm_compiler_rt_build.to_string_lossy().as_ref(),
"-G",
generator,
"Ninja",
])
.args(CMAKE_STATIC_ARGS)
.args(cmake_dynamic_args(build_type, target_env)?)
+1 -1
View File
@@ -38,7 +38,7 @@ pub const MUSL_SNAPSHOTS_URL: &str = "https://git.musl-libc.org/cgit/musl/snapsh
pub const EMSDK_SOURCE_URL: &str = "https://github.com/emscripten-core/emsdk.git";
/// The emscripten SDK version.
pub const EMSDK_VERSION: &str = "3.1.64";
pub const EMSDK_VERSION: &str = "4.0.9";
/// The subprocess runner.
///
-2
View File
@@ -5,7 +5,6 @@ use std::sync::OnceLock;
pub use self::debug_config::ir_type::IRType as DebugConfigIR;
pub use self::debug_config::DebugConfig;
pub use self::memory::MemoryConfig;
pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer;
@@ -76,7 +75,6 @@ pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine;
pub(crate) mod debug_config;
pub(crate) mod memory;
pub(crate) mod optimizer;
pub(crate) mod polkavm;
pub(crate) mod target_machine;
@@ -234,7 +234,7 @@ impl TryFrom<&SolcStandardJsonInputSettingsOptimizer> for Settings {
fn try_from(value: &SolcStandardJsonInputSettingsOptimizer) -> Result<Self, Self::Error> {
let mut result = match value.mode {
Some(mode) => Self::try_from_cli(mode)?,
None => Self::cycles(),
None => Self::size(),
};
if value.fallback_to_optimizing_for_size.unwrap_or_default() {
result.enable_fallback_to_size();
@@ -25,8 +25,8 @@ use inkwell::debug_info::AsDIScope;
use inkwell::debug_info::DIScope;
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer;
use crate::polkavm::DebugConfig;
@@ -88,7 +88,7 @@ where
/// The extra LLVM arguments that were used during target initialization.
llvm_arguments: &'ctx [String],
/// The PVM memory configuration.
memory_config: MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
/// The project dependency manager. It can be any entity implementing the trait.
/// The manager is used to get information about contracts and their dependencies during
@@ -228,7 +228,7 @@ where
include_metadata_hash: bool,
debug_config: DebugConfig,
llvm_arguments: &'ctx [String],
memory_config: MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> Self {
Self::set_data_layout(llvm, &module);
Self::link_stdlib_module(llvm, &module);
+3 -3
View File
@@ -7,12 +7,12 @@ pub mod evm;
pub use self::r#const::*;
use crate::debug_config::DebugConfig;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings;
use anyhow::Context as AnyhowContext;
use polkavm_common::program::ProgramBlob;
use polkavm_disassembler::{Disassembler, DisassemblyFormat};
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
use sha3::Digest;
use self::context::build::Build;
@@ -91,7 +91,7 @@ pub trait Dependency {
include_metadata_hash: bool,
debug_config: DebugConfig,
llvm_arguments: &[String],
memory_config: MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<String>;
/// Resolves a full contract path.
@@ -113,7 +113,7 @@ impl Dependency for DummyDependency {
_include_metadata_hash: bool,
_debug_config: DebugConfig,
_llvm_arguments: &[String],
_memory_config: MemoryConfig,
_memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<String> {
Ok(String::new())
}
+1 -2
View File
@@ -16,7 +16,7 @@ Inside the root `revive` repository directory, execute:
make install-revive-runner
```
Which will install the `revive-runner` using `cargo`.
Which will install the `revive-runner` using `cargo`.
## Usage
@@ -24,4 +24,3 @@ Set the `RUST_LOG` environment varibale to the `trace` level to see the full Pol
```bash
RUST_LOG=trace revive-runner -f mycontract.pvm -c a9059cbb000000000000000000000000f24ff3a9cf04c71dbc94d0b566f7a27b94566cac0000000000000000000000000000000000000000000000000000000000000000
```
+7 -1
View File
@@ -94,6 +94,12 @@ impl FindAuthor<<Runtime as frame_system::Config>::AccountId> for Runtime {
where
I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>,
{
Some([0xff; 32].into())
Some(
[[0xff; 20].as_slice(), [0xee; 12].as_slice()]
.concat()
.as_slice()
.try_into()
.unwrap(),
)
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ description = "Implements the low level runtime API bindings with pallet contrac
[dependencies]
anyhow = { workspace = true }
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] }
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-1"] }
revive-common = { workspace = true }
+2
View File
@@ -8,6 +8,8 @@ pub use self::standard_json::input::language::Language as SolcStandardJsonInputL
pub use self::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
pub use self::standard_json::input::settings::metadata_hash::MetadataHash as SolcStandardJsonInputSettingsMetadataHash;
pub use self::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
pub use self::standard_json::input::settings::polkavm::memory::MemoryConfig as SolcStandardJsonInputSettingsPolkaVMMemory;
pub use self::standard_json::input::settings::polkavm::PolkaVM as SolcStandardJsonInputSettingsPolkaVM;
pub use self::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag;
pub use self::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile;
pub use self::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -18,6 +18,7 @@ use crate::standard_json::input::settings::optimizer::Optimizer as SolcStandardJ
use crate::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
#[cfg(feature = "resolc")]
use crate::warning::Warning;
use crate::SolcStandardJsonInputSettingsPolkaVM;
use self::language::Language;
use self::settings::Settings;
@@ -63,6 +64,7 @@ impl Input {
optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
#[cfg(feature = "resolc")] suppressed_warnings: Option<Vec<Warning>>,
polkavm: Option<SolcStandardJsonInputSettingsPolkaVM>,
) -> anyhow::Result<Self> {
let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect();
let libraries = Settings::parse_libraries(library_map)?;
@@ -90,6 +92,7 @@ impl Input {
output_selection,
optimizer,
metadata,
polkavm,
),
#[cfg(feature = "resolc")]
suppressed_warnings,
@@ -109,6 +112,7 @@ impl Input {
optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
suppressed_warnings: Option<Vec<Warning>>,
polkavm: Option<SolcStandardJsonInputSettingsPolkaVM>,
) -> anyhow::Result<Self> {
#[cfg(feature = "parallel")]
let iter = sources.into_par_iter(); // Parallel iterator
@@ -129,6 +133,7 @@ impl Input {
output_selection,
optimizer,
metadata,
polkavm,
),
suppressed_warnings,
})
@@ -3,6 +3,7 @@
pub mod metadata;
pub mod metadata_hash;
pub mod optimizer;
pub mod polkavm;
pub mod selection;
use std::collections::BTreeMap;
@@ -13,6 +14,7 @@ use serde::Serialize;
use self::metadata::Metadata;
use self::optimizer::Optimizer;
use self::polkavm::PolkaVM;
use self::selection::Selection;
/// The `solc --standard-json` input settings.
@@ -43,6 +45,9 @@ pub struct Settings {
/// The metadata settings.
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
/// The resolc custom PolkaVM settings.
#[serde(skip_serializing)]
pub polkavm: Option<PolkaVM>,
}
impl Settings {
@@ -54,6 +59,7 @@ impl Settings {
output_selection: Selection,
optimizer: Optimizer,
metadata: Option<Metadata>,
polkavm: Option<PolkaVM>,
) -> Self {
Self {
evm_version,
@@ -63,6 +69,7 @@ impl Settings {
optimizer,
metadata,
via_ir: Some(true),
polkavm,
}
}
@@ -0,0 +1,26 @@
//! The `resolc --standard-json` polkavm settings.
//!
//! Used for options specific to PolkaVM which therefor don't exist in solc.
use memory::MemoryConfig;
use serde::{Deserialize, Serialize};
pub mod memory;
/// PVM specific compiler settings.
#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)]
pub struct PolkaVM {
/// The PolkaVM target machine memory configuration settings.
pub memory_config: MemoryConfig,
/// Instruct LLVM to emit debug information.
pub debug_information: bool,
}
impl PolkaVM {
pub fn new(memory_config: Option<MemoryConfig>, debug_information: bool) -> Self {
Self {
memory_config: memory_config.unwrap_or_default(),
debug_information,
}
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ mimalloc = { version = "*", default-features = false }
[target.'cfg(target_os = "emscripten")'.dependencies]
libc = { workspace = true }
inkwell = { workspace = true, features = ["target-riscv", "llvm18-0-no-llvm-linking"]}
inkwell = { workspace = true, features = ["target-riscv", "llvm18-1-no-llvm-linking"]}
[build-dependencies]
git2 = { workspace = true, default-features = false }
+15 -7
View File
@@ -45,6 +45,8 @@ use revive_solc_json_interface::ResolcWarning;
use revive_solc_json_interface::SolcStandardJsonInput;
use revive_solc_json_interface::SolcStandardJsonInputLanguage;
use revive_solc_json_interface::SolcStandardJsonInputSettingsOptimizer;
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVM;
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
use revive_solc_json_interface::SolcStandardJsonInputSettingsSelection;
/// Runs the Yul mode.
@@ -55,7 +57,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<Build> {
let path = match input_files.len() {
1 => input_files.first().expect("Always exists"),
@@ -94,7 +96,7 @@ pub fn llvm_ir(
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<Build> {
let path = match input_files.len() {
1 => input_files.first().expect("Always exists"),
@@ -135,7 +137,7 @@ pub fn standard_output<T: Compiler>(
suppressed_warnings: Option<Vec<ResolcWarning>>,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<Build> {
let solc_version = solc.version()?;
@@ -154,6 +156,10 @@ pub fn standard_output<T: Compiler>(
),
None,
suppressed_warnings,
Some(SolcStandardJsonInputSettingsPolkaVM::new(
Some(memory_config),
debug_config.emit_debug_info,
)),
)?;
let source_code_files = solc_input
@@ -208,9 +214,8 @@ pub fn standard_json<T: Compiler>(
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig,
mut debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> {
let solc_version = solc.version()?;
@@ -224,6 +229,9 @@ pub fn standard_json<T: Compiler>(
let optimizer_settings =
revive_llvm_context::OptimizerSettings::try_from(&solc_input.settings.optimizer)?;
let polkavm_settings = solc_input.settings.polkavm.unwrap_or_default();
debug_config.emit_debug_info = polkavm_settings.debug_information;
let include_metadata_hash = match solc_input.settings.metadata {
Some(ref metadata) => metadata.bytecode_hash != Some(MetadataHash::None),
None => true,
@@ -258,7 +266,7 @@ pub fn standard_json<T: Compiler>(
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
polkavm_settings.memory_config,
)?;
build.write_to_standard_json(&mut solc_output, &solc_version)?;
}
@@ -286,7 +294,7 @@ pub fn combined_json<T: Compiler>(
output_directory: Option<PathBuf>,
overwrite: bool,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<()> {
let build = standard_output(
input_files,
+3 -2
View File
@@ -1,6 +1,7 @@
//! Process for compiling a single compilation unit.
//! The input data.
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
use serde::Deserialize;
use serde::Serialize;
@@ -23,7 +24,7 @@ pub struct Input {
/// The extra LLVM arguments give used for manual control.
pub llvm_arguments: Vec<String>,
/// The PVM memory configuration.
pub memory_config: revive_llvm_context::MemoryConfig,
pub memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
}
impl Input {
@@ -35,7 +36,7 @@ impl Input {
optimizer_settings: revive_llvm_context::OptimizerSettings,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: Vec<String>,
memory_config: revive_llvm_context::MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> Self {
Self {
contract,
+2 -1
View File
@@ -5,6 +5,7 @@ pub mod metadata;
use std::collections::HashSet;
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
use serde::Deserialize;
use serde::Serialize;
use sha3::Digest;
@@ -78,7 +79,7 @@ impl Contract {
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<ContractBuild> {
let llvm = inkwell::context::Context::create();
let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings);
+2 -2
View File
@@ -67,7 +67,7 @@ impl Project {
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<Build> {
let project = self.clone();
#[cfg(feature = "parallel")]
@@ -321,7 +321,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
memory_config: revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory,
) -> anyhow::Result<String> {
let contract_path = project.resolve_path(identifier)?;
let contract = project
+20 -4
View File
@@ -185,8 +185,8 @@ pub struct Arguments {
/// 1.Increasing the heap size will increase startup costs.
/// 2.The heap size contributes to the total memory size a contract can use,
/// which includes the contracts code size
#[arg(long = "heap-size", default_value = "65536")]
pub heap_size: u32,
#[arg(long = "heap-size")]
pub heap_size: Option<u32>,
/// The contracts total stack size in bytes.
///
@@ -200,8 +200,8 @@ pub struct Arguments {
/// 1.Increasing the heap size will increase startup costs.
/// 2.The stack size contributes to the total memory size a contract can use,
/// which includes the contracts code size
#[arg(long = "stack-size", default_value = "32768")]
pub stack_size: u32,
#[arg(long = "stack-size")]
pub stack_size: Option<u32>,
}
impl Arguments {
@@ -330,6 +330,22 @@ impl Arguments {
if self.metadata_hash.is_some() {
anyhow::bail!("Metadata hash mode must specified in standard JSON input settings.");
}
if self.heap_size.is_some() {
anyhow::bail!(
"Heap size must be specified in standard JSON input polkavm memory settings."
);
}
if self.stack_size.is_some() {
anyhow::bail!(
"Stack size must be specified in standard JSON input polkavm memory settings."
);
}
if self.emit_source_debug_info {
anyhow::bail!(
"Debug info must be requested in standard JSON input polkavm settings."
);
}
}
Ok(())
+9 -6
View File
@@ -129,7 +129,7 @@ fn main_inner() -> anyhow::Result<()> {
let mut optimizer_settings = match arguments.optimization {
Some(mode) => revive_llvm_context::OptimizerSettings::try_from_cli(mode)?,
None => revive_llvm_context::OptimizerSettings::cycles(),
None => revive_llvm_context::OptimizerSettings::size(),
};
if arguments.fallback_to_optimizing_for_size {
optimizer_settings.enable_fallback_to_size();
@@ -148,10 +148,14 @@ fn main_inner() -> anyhow::Result<()> {
None => true,
};
let memory_config = revive_llvm_context::MemoryConfig {
heap_size: arguments.heap_size,
stack_size: arguments.stack_size,
};
let mut memory_config =
revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory::default();
if let Some(heap_size) = arguments.heap_size {
memory_config.heap_size = heap_size
}
if let Some(stack_size) = arguments.stack_size {
memory_config.stack_size = stack_size
}
let build = if arguments.yul {
revive_solidity::yul(
@@ -181,7 +185,6 @@ fn main_inner() -> anyhow::Result<()> {
arguments.allow_paths,
debug_config,
&arguments.llvm_arguments,
memory_config,
)?;
return Ok(());
} else if let Some(format) = arguments.combined_json {
+1 -1
View File
@@ -19,7 +19,7 @@ use self::version::Version;
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 29);
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 30);
/// `--include-path` was introduced in solc `0.8.8` <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
pub const FIRST_INCLUDE_PATH_VERSION: semver::Version = semver::Version::new(0, 8, 8);
+4
View File
@@ -98,6 +98,7 @@ pub fn build_solidity_with_options(
),
None,
None,
None,
)?;
let mut output = solc.standard_json(input, None, vec![], None)?;
@@ -162,6 +163,7 @@ pub fn build_solidity_with_options_evm(
),
None,
None,
None,
)?;
let mut output = solc.standard_json(input, None, vec![], None)?;
@@ -213,6 +215,7 @@ pub fn build_solidity_and_detect_missing_libraries(
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None,
None,
None,
)?;
let mut output = solc.standard_json(input, None, vec![], None)?;
@@ -286,6 +289,7 @@ pub fn check_solidity_warning(
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None,
suppressed_warnings,
None,
)?;
let output = solc.standard_json(input, None, vec![], None)?;
@@ -23,4 +23,4 @@
"ts-jest": "^29.2.5",
"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 binExtension = ":C.pvm";
const asmExtension = ":C.pvmasm";
const llvmExtension = ".ll";
const contractSolFilename = "contract.sol";
const contractYulFilename = "contract.yul";
const contractOptimizedLLVMFilename = contractSolFilename + ".C.optimized";
const contractUnoptimizedLLVMFilename = contractSolFilename + ".C.unoptimized";
const pathToOutputDir = path.join(__dirname, "..", outputDir);
const pathToContracts = path.join(__dirname, "..", "src", "contracts");
const outputDir = 'artifacts'
const binExtension = ':C.pvm'
const asmExtension = ':C.pvmasm'
const llvmExtension = '.ll'
const contractSolFilename = 'contract.sol'
const contractYulFilename = 'contract.yul'
const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized'
const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized'
const pathToOutputDir = path.join(__dirname, '..', outputDir)
const pathToContracts = path.join(__dirname, '..', 'src', 'contracts')
const pathToBasicYulContract = path.join(
pathToContracts,
"yul",
contractYulFilename,
);
'yul',
contractYulFilename
)
const pathToBasicSolContract = path.join(
pathToContracts,
"solidity",
contractSolFilename,
);
'solidity',
contractSolFilename
)
const pathToSolBinOutputFile = path.join(
pathToOutputDir,
contractSolFilename + binExtension,
);
contractSolFilename + binExtension
)
const pathToSolAsmOutputFile = path.join(
pathToOutputDir,
contractSolFilename + asmExtension,
);
contractSolFilename + asmExtension
)
export const paths = {
outputDir: outputDir,
@@ -44,4 +44,4 @@ export const paths = {
pathToBasicYulContract: pathToBasicYulContract,
pathToSolBinOutputFile: pathToSolBinOutputFile,
pathToSolAsmOutputFile: pathToSolAsmOutputFile,
};
}
@@ -1,51 +1,51 @@
import * as shell from "shelljs";
import * as fs from "fs";
import { spawnSync } from "child_process";
import * as shell from 'shelljs'
import * as fs from 'fs'
import { spawnSync } from 'child_process'
interface CommandResult {
output: string;
exitCode: number;
output: string
exitCode: number
}
export const executeCommand = (
command: string,
stdin?: string,
stdin?: string
): CommandResult => {
if (stdin) {
const process = spawnSync(command, [], {
input: stdin,
shell: true,
encoding: "utf8",
encoding: 'utf8',
maxBuffer: 30 * 1024 * 1024,
});
})
return {
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 {
exitCode: result.code,
output: result.stdout || result.stderr || "",
};
};
output: result.stdout || result.stderr || '',
}
}
export const isFolderExist = (folder: string): boolean => {
return shell.test("-d", folder);
};
return shell.test('-d', folder)
}
export const isFileExist = (
pathToFileDir: string,
fileName: string,
fileExtension: string,
fileExtension: string
): boolean => {
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension);
};
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension)
}
export const isFileEmpty = (file: string): boolean => {
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 { paths } from "../src/entities";
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
//id1746
describe("Run with --asm by default", () => {
const command = `resolc ${paths.pathToBasicSolContract} --asm`;
const result = executeCommand(command);
const commandInvalid = "resolc --asm";
const resultInvalid = executeCommand(commandInvalid);
describe('Run with --asm by default', () => {
const command = `resolc ${paths.pathToBasicSolContract} --asm`
const result = executeCommand(command)
const commandInvalid = 'resolc --asm'
const resultInvalid = executeCommand(commandInvalid)
it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it("--asm output is presented", () => {
const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i];
it('--asm output is presented', () => {
const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i]
for (const pattern of expectedPatterns) {
expect(result.output).toMatch(pattern);
expect(result.output).toMatch(pattern)
}
});
})
it("solc exit code == resolc exit code", () => {
const command = `solc ${paths.pathToBasicSolContract} --asm`;
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode);
});
it('solc exit code == resolc exit code', () => {
const command = `solc ${paths.pathToBasicSolContract} --asm`
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
it("run invalid: resolc --asm", () => {
it('run invalid: resolc --asm', () => {
expect(resultInvalid.output).toMatch(
/(No input sources specified|Compilation aborted)/i,
);
});
/(No input sources specified|Compilation aborted)/i
)
})
it("Invalid command exit code = 1", () => {
expect(resultInvalid.exitCode).toBe(1);
});
it('Invalid command exit code = 1', () => {
expect(resultInvalid.exitCode).toBe(1)
})
it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = "solc --asm";
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
});
});
it('Invalid solc exit code == Invalid resolc exit code', () => {
const command = 'solc --asm'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(resultInvalid.exitCode)
})
})
@@ -3,239 +3,239 @@ import {
isFolderExist,
isFileExist,
isFileEmpty,
} from "../src/helper";
import { paths } from "../src/entities";
import * as shell from "shelljs";
import * as path from "path";
} from '../src/helper'
import { paths } from '../src/entities'
import * as shell from 'shelljs'
import * as path from 'path'
//id1762
describe("Run resolc without any options", () => {
const command = "resolc";
const result = executeCommand(command);
describe('Run resolc without any options', () => {
const command = 'resolc'
const result = executeCommand(command)
it("Info with help is presented", () => {
expect(result.output).toMatch(/(Usage: resolc)/i);
});
it('Info with help is presented', () => {
expect(result.output).toMatch(/(Usage: resolc)/i)
})
it("Exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it('Exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("solc exit code == resolc exit code", () => {
const command = "solc";
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == resolc exit code', () => {
const command = 'solc'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//#1713
describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command);
describe('Default run a command from the help', () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"` // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command)
it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
xit("Output file is created", () => {
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
xit('Output file is created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension,
),
).toBe(true);
});
it("the output file is not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
});
paths.binExtension
)
).toBe(true)
})
it('the output file is not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
})
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
});
});
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i)
})
})
//#1818
describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command);
describe('Default run a command from the help', () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"` // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command)
it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
xit("Output files are created", () => {
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
xit('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension,
),
).toBe(true);
paths.binExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension,
),
).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
paths.asmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
});
});
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 = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
]; // potential issue on resolc with full path on Windows cmd`;
] // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
const command = commands[idx];
const result = executeCommand(command);
const command = commands[idx]
const result = executeCommand(command)
it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
it("Output files are created", () => {
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
it('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension,
),
).toBe(true);
paths.binExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension,
),
).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
paths.asmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
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 = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
]; // potential issue on resolc with full path on Windows cmd`;
] // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
const command = commands[idx];
const result = executeCommand(command);
const command = commands[idx]
const result = executeCommand(command)
it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
it("Output files are created", () => {
it('Compiler run successful', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
it('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractOptimizedLLVMFilename,
paths.llvmExtension,
),
).toBe(true);
paths.llvmExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractUnoptimizedLLVMFilename,
paths.llvmExtension,
),
).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
paths.llvmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i)
})
}
});
})
describe("Standard JSON compilation with path options", () => {
const contractsDir = path.join(shell.tempdir(), "contracts-test");
const inputFile = path.join(__dirname, "..", "src/contracts/compiled/1.json");
describe('Standard JSON compilation with path options', () => {
const contractsDir = path.join(shell.tempdir(), 'contracts-test')
const inputFile = path.join(__dirname, '..', 'src/contracts/compiled/1.json')
beforeAll(() => {
shell.mkdir("-p", contractsDir);
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(
([sourcePath, source]: [string, any]) => {
const filePath = path.join(contractsDir, sourcePath);
shell.mkdir("-p", path.dirname(filePath));
shell.ShellString(source.content).to(filePath);
},
);
});
const filePath = path.join(contractsDir, sourcePath)
shell.mkdir('-p', path.dirname(filePath))
shell.ShellString(source.content).to(filePath)
}
)
})
afterAll(() => {
shell.rm("-rf", contractsDir);
});
shell.rm('-rf', contractsDir)
})
describe("Output with all path options", () => {
let result: { exitCode: number; output: string };
describe('Output with all path options', () => {
let result: { exitCode: number; output: string }
beforeAll(() => {
const tempInputFile = path.join(contractsDir, "temp-input.json");
shell.cp(inputFile, tempInputFile);
const inputContent = shell.cat(inputFile).toString();
const tempInputFile = path.join(contractsDir, 'temp-input.json')
shell.cp(inputFile, tempInputFile)
const inputContent = shell.cat(inputFile).toString()
const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`;
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", () => {
const parsedResults = JSON.parse(result.output);
it('Compiler run successful without emiting warnings', () => {
const parsedResults = JSON.parse(result.output)
expect(
parsedResults.errors.filter(
(error: { type: string }) => error.type != "Warning",
),
).toEqual([]);
});
});
});
(error: { type: string }) => error.type != 'Warning'
)
).toEqual([])
})
})
})
@@ -1,39 +1,39 @@
import { executeCommand } from "../src/helper";
import { paths } from "../src/entities";
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
//id1743
describe("Run with --yul by default", () => {
const command = `resolc ${paths.pathToBasicYulContract} --yul`;
const result = executeCommand(command);
const commandInvalid = "resolc --yul";
const resultInvalid = executeCommand(commandInvalid);
describe('Run with --yul by default', () => {
const command = `resolc ${paths.pathToBasicYulContract} --yul`
const result = executeCommand(command)
const commandInvalid = 'resolc --yul'
const resultInvalid = executeCommand(commandInvalid)
it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it("--yul output is presented", () => {
expect(result.output).toMatch(/(Compiler run successful)/i);
expect(result.output).toMatch(/(No output requested)/i);
});
it('--yul output is presented', () => {
expect(result.output).toMatch(/(Compiler run successful)/i)
expect(result.output).toMatch(/(No output requested)/i)
})
xit("solc exit code == resolc exit code", () => {
xit('solc exit code == resolc exit code', () => {
// unknown solc issue for datatype of the contract
const command = `solc ${paths.pathToBasicSolContract} --yul`;
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode);
});
const command = `solc ${paths.pathToBasicSolContract} --yul`
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
it("run invalid: resolc --yul", () => {
expect(resultInvalid.output).toMatch(/(The input file is missing)/i);
});
it("Invalid command exit code = 1", () => {
expect(resultInvalid.exitCode).toBe(1);
});
it('run invalid: resolc --yul', () => {
expect(resultInvalid.output).toMatch(/(The input file is missing)/i)
})
it('Invalid command exit code = 1', () => {
expect(resultInvalid.exitCode).toBe(1)
})
it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = "solc --yul";
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
});
});
it('Invalid solc exit code == Invalid resolc exit code', () => {
const command = 'solc --yul'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(resultInvalid.exitCode)
})
})
@@ -2,6 +2,6 @@
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"outDir": "./dist"
"outDir": "./dist",
}
}
+100 -104
View File
@@ -1,9 +1,9 @@
import { executeCommand } from "../src/helper";
import { paths } from "../src/entities";
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
describe("Set of --combined-json tests", () => {
const zksolcCommand = "zksolc";
const solcCommand = "solc";
describe('Set of --combined-json tests', () => {
const zksolcCommand = 'zksolc'
const solcCommand = 'solc'
const json_args: string[] = [
`abi`,
`hashes`,
@@ -15,49 +15,45 @@ describe("Set of --combined-json tests", () => {
`asm`,
`bin`,
`bin-runtime`,
];
]
//id1742:I
describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const args = [`--combined-json`];
const result = executeCommand(zksolcCommand, args);
const args = [`--combined-json`]
const result = executeCommand(zksolcCommand, args)
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("--combined-json error is presented", () => {
expect(result.output).toMatch(
/(requires a value but none was supplied)/i,
);
});
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i)
})
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//id1742:II
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`];
const result = executeCommand(zksolcCommand, args);
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`]
const result = executeCommand(zksolcCommand, args)
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("--combined-json error is presented", () => {
expect(result.output).toMatch(
/(requires a value but none was supplied)/i,
);
});
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i)
})
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//id1742:III
for (let i = 0; i < json_args.length; i++) {
@@ -66,22 +62,22 @@ describe("Set of --combined-json tests", () => {
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
]
const result = executeCommand(zksolcCommand, args)
it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(contracts)/i);
});
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(contracts)/i)
})
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1829:I
@@ -91,22 +87,22 @@ describe("Set of --combined-json tests", () => {
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`--${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
]
const result = executeCommand(zksolcCommand, args)
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(Invalid option|error)/i);
});
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(Invalid option|error)/i)
})
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1829:II
@@ -117,24 +113,24 @@ describe("Set of --combined-json tests", () => {
`--combined-json`,
`${json_args[i]}`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
]
const result = executeCommand(zksolcCommand, args)
xit("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
xit('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("--combined-json error is presented", () => {
it('--combined-json error is presented', () => {
expect(result.output).toMatch(
/(No such file or directory|cannot find the file specified)/i,
); // Hopefully we should have more precise message here!
});
/(No such file or directory|cannot find the file specified)/i
) // Hopefully we should have more precise message here!
})
xit("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
xit('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1829:III
@@ -146,22 +142,22 @@ describe("Set of --combined-json tests", () => {
`${json_args[i]}`,
`--combined-json`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
]
const result = executeCommand(zksolcCommand, args)
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i);
});
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i)
})
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
//id1830
@@ -171,22 +167,22 @@ describe("Set of --combined-json tests", () => {
`${paths.pathToBasicYulContract}`,
`--combined-json`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
]
const result = executeCommand(zksolcCommand, args)
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it('Valid command exit code = 1', () => {
expect(result.exitCode).toBe(1)
})
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i);
});
asd;
it('--combined-json error is presented', () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i)
})
asd
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == zksolc exit code', () => {
const solcResult = executeCommand(solcCommand, args)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
});
})
+1 -1
View File
@@ -7,7 +7,7 @@ repository.workspace = true
description = "revive compiler stdlib components"
[dependencies]
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] }
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-1"] }
[build-dependencies]
revive-build-utils = { workspace = true }
@@ -4,8 +4,8 @@ import tseslint from 'typescript-eslint'
/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ['**/*.{mjs,ts}'] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{ files: ['**/*.{mjs,ts}'] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
]
+1 -1
View File
@@ -3,7 +3,7 @@ const path = require("path");
const { minify } = require("terser");
const SOLJSON_URI =
"https://binaries.soliditylang.org/wasm/soljson-v0.8.29+commit.ab55807c.js";
"https://binaries.soliditylang.org/wasm/soljson-v0.8.30+commit.73712a01.js";
const RESOLC_WASM_URI =
process.env.RELEASE_RESOLC_WASM_URI || "http://127.0.0.1:8080/resolc.wasm";
const RESOLC_WASM_TARGET_DIR = path.join(
+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
@@ -2,7 +2,7 @@
"name": "revive",
"private": true,
"dependencies": {
"solc": "^0.8.29"
"solc": ">=0.8.0 <=0.8.30"
},
"scripts": {
"example:web": "http-server ./examples/web/",
+138
View File
@@ -0,0 +1,138 @@
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
@@ -1,6 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
+2 -5
View File
@@ -1,10 +1,7 @@
# @parity/revive
Node.js module to compile Solidity contracts to Polkavm bytecode, using [Revive](https://github.com/paritytech/revive)
# Usage from Node.js
```typescript
import { compile } from "@parity/resolc";
const sources = {
["contracts/1_Storage.sol"]: {
content: readFileSync("fixtures/storage.sol", "utf8"),
@@ -16,5 +13,5 @@ const out = await compile(sources);
# Usage from shell
```bash
npx @parity/revive@latest --bin contracts/1_Storage.sol
npx @parity/resolc@latest --bin contracts/1_Storage.sol
```
+5 -9
View File
@@ -1,7 +1,7 @@
{
"name": "@parity/resolc",
"license": "Apache-2.0",
"version": "0.0.0-updated-via-gh-releases",
"version": "0.1.0-dev.16",
"author": "Parity <admin@parity.io> (https://parity.io)",
"module": "index.ts",
"types": "./dist/index.d.ts",
@@ -24,18 +24,14 @@
"test": "npm run build && node ./dist/index.test.js"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"@openzeppelin/contracts": "5.1.0",
"@types/node": "^22.14.1",
"eslint": "^9.14.0",
"globals": "^15.12.0",
"typescript": "^5.6.3",
"typescript-eslint": "^8.13.0"
"typescript": "^5.6.3"
},
"dependencies": {
"@types/node": "^22.9.0",
"commander": "^13.1.0",
"package-json": "^10.0.1",
"solc": "^0.8.29"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"solc": ">=0.8.0 <=0.8.30"
}
}
+169 -172
View File
@@ -8,196 +8,193 @@ import * as resolc from '.'
import { SolcInput } from '.'
async function main() {
// hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
const originalUncaughtExceptionListeners =
process.listeners('uncaughtException')
// FIXME: remove annoying exception catcher of Emscripten
// see https://github.com/chriseth/browser-solidity/issues/167
process.removeAllListeners('uncaughtException')
// hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
const originalUncaughtExceptionListeners =
process.listeners('uncaughtException')
// FIXME: remove annoying exception catcher of Emscripten
// see https://github.com/chriseth/browser-solidity/issues/167
process.removeAllListeners('uncaughtException')
const program = new commander.Command()
const program = new commander.Command()
program.name('solcjs')
program.version(resolc.version())
program
.option('--bin', 'Binary of the contracts in hex.')
.option('--abi', 'ABI of the contracts.')
.option(
'--base-path <path>',
'Root of the project source tree. ' +
'The import callback will attempt to interpret all import paths as relative to this directory.'
)
.option(
'--include-path <path...>',
'Extra source directories available to the import callback. ' +
'When using a package manager to install libraries, use this option to specify directories where packages are installed. ' +
'Can be used multiple times to provide multiple locations.'
)
.option(
'-o, --output-dir <output-directory>',
'Output directory for the contracts.'
)
.option('-p, --pretty-json', 'Pretty-print all JSON output.', false)
.option('-v, --verbose', 'More detailed console output.', false)
.argument('<files...>')
program.name('solcjs')
program.version(resolc.version())
program
.option('--bin', 'Binary of the contracts in hex.')
.option('--abi', 'ABI of the contracts.')
.option(
'--base-path <path>',
'Root of the project source tree. ' +
'The import callback will attempt to interpret all import paths as relative to this directory.'
)
.option(
'--include-path <path...>',
'Extra source directories available to the import callback. ' +
'When using a package manager to install libraries, use this option to specify directories where packages are installed. ' +
'Can be used multiple times to provide multiple locations.'
)
.option(
'-o, --output-dir <output-directory>',
'Output directory for the contracts.'
)
.option('-p, --pretty-json', 'Pretty-print all JSON output.', false)
.option('-v, --verbose', 'More detailed console output.', false)
.argument('<files...>')
program.parse(process.argv)
const options = program.opts<{
verbose: boolean
abi: boolean
bin: boolean
outputDir?: string
prettyJson: boolean
basePath?: string
includePath?: string[]
}>()
const files: string[] = program.args
const destination = options.outputDir ?? '.'
program.parse(process.argv)
const options = program.opts<{
verbose: boolean
abi: boolean
bin: boolean
outputDir?: string
prettyJson: boolean
basePath?: string
includePath?: string[]
}>()
const files: string[] = program.args
const destination = options.outputDir ?? '.'
function abort(msg: string) {
console.error(msg || 'Error occurred')
process.exit(1)
function abort(msg: string) {
console.error(msg || 'Error occurred')
process.exit(1)
}
function withUnixPathSeparators(filePath: string) {
// On UNIX-like systems forward slashes in paths are just a part of the file name.
if (os.platform() !== 'win32') {
return filePath
}
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, '/')
}
return filePath.replace(/\\/g, '/')
}
function makeSourcePathRelativeIfPossible(sourcePath: string) {
const absoluteBasePath = options.basePath
? path.resolve(options.basePath)
: path.resolve('.')
const absoluteIncludePaths = options.includePath
? options.includePath.map((prefix: string) => {
return path.resolve(prefix)
})
: []
// Compared to base path stripping logic in solc this is much simpler because path.resolve()
// handles symlinks correctly (does not resolve them except in work dir) and strips .. segments
// from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it
// ignores less important corner cases: drive letters are not stripped from absolute paths on
// Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has
// very little test coverage so there might be more differences that we are just not aware of.
const absoluteSourcePath = path.resolve(sourcePath)
for (const absolutePrefix of [absoluteBasePath].concat(
absoluteIncludePaths
)) {
const relativeSourcePath = path.relative(
absolutePrefix,
absoluteSourcePath
)
if (!relativeSourcePath.startsWith('../')) {
return withUnixPathSeparators(relativeSourcePath)
}
}
// File is not located inside base path or include paths so use its absolute path.
return withUnixPathSeparators(absoluteSourcePath)
}
function toFormattedJson<T>(input: T) {
return JSON.stringify(input, null, options.prettyJson ? 4 : 0)
}
if (files.length === 0) {
console.error('Must provide a file')
process.exit(1)
}
if (!(options.bin || options.abi)) {
abort('Invalid option selected, must specify either --bin or --abi')
}
const sources: SolcInput = {}
for (let i = 0; i < files.length; i++) {
try {
sources[makeSourcePathRelativeIfPossible(files[i])] = {
content: fs.readFileSync(files[i]).toString(),
}
} catch (e) {
abort('Error reading ' + files[i] + ': ' + e)
}
}
if (options.verbose) {
console.log('>>> Compiling:\n' + toFormattedJson(sources) + '\n')
}
const output = await resolc.compile(sources)
let hasError = false
if (!output) {
abort('No output from compiler')
} else if (output.errors) {
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)
}
function makeSourcePathRelativeIfPossible(sourcePath: string) {
const absoluteBasePath = options.basePath
? path.resolve(options.basePath)
: path.resolve('.')
const absoluteIncludePaths = options.includePath
? options.includePath.map((prefix: string) => {
return path.resolve(prefix)
})
: []
// Compared to base path stripping logic in solc this is much simpler because path.resolve()
// handles symlinks correctly (does not resolve them except in work dir) and strips .. segments
// from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it
// ignores less important corner cases: drive letters are not stripped from absolute paths on
// Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has
// very little test coverage so there might be more differences that we are just not aware of.
const absoluteSourcePath = path.resolve(sourcePath)
for (const absolutePrefix of [absoluteBasePath].concat(
absoluteIncludePaths
)) {
const relativeSourcePath = path.relative(
absolutePrefix,
absoluteSourcePath
)
if (!relativeSourcePath.startsWith('../')) {
return withUnixPathSeparators(relativeSourcePath)
}
}
for (const fileName in output.contracts) {
for (const contractName in output.contracts[fileName]) {
let contractFileName = fileName + ':' + contractName
contractFileName = contractFileName.replace(/[:./\\]/g, '_')
// File is not located inside base path or include paths so use its absolute path.
return withUnixPathSeparators(absoluteSourcePath)
}
if (options.bin) {
writeFile(
contractFileName + '.polkavm',
Buffer.from(
output.contracts[fileName][contractName].evm.bytecode
.object,
'hex'
)
)
}
function toFormattedJson<T>(input: T) {
return JSON.stringify(input, null, options.prettyJson ? 4 : 0)
}
if (options.abi) {
writeFile(
contractFileName + '.abi',
toFormattedJson(
output.contracts[fileName][contractName].abi
)
)
}
}
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)
}
}
// Put back original exception handlers.
originalUncaughtExceptionListeners.forEach(function (listener) {
process.addListener('uncaughtException', listener)
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)
}
})
}
if (hasError) {
process.exit(1)
for (const fileName in output.contracts) {
for (const contractName in output.contracts[fileName]) {
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) => {
console.error('Error:', err)
process.exit(1)
console.error('Error:', err)
process.exit(1)
})
+90 -106
View File
@@ -1,132 +1,116 @@
import { test } from 'node:test'
import { readFileSync } from 'node:fs'
import { readFileSync, existsSync } from 'node:fs'
import assert from 'node:assert'
import { compile, tryResolveImport } from '.'
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 = [{}]
if (isResolcInPath()) {
compileOptions.push({ bin: 'resolc' })
if (existsSync('../../target/release/resolc')) {
compileOptions.push({ bin: '../../target/release/resolc' })
}
for (const options of compileOptions) {
test(`check Ok output with option ${JSON.stringify(options)}`, async () => {
const contract = 'fixtures/token.sol'
const sources = {
[contract]: {
content: readFileSync('fixtures/storage.sol', 'utf8'),
},
}
test(`check Ok output with option ${JSON.stringify(options)}`, async () => {
const contract = 'fixtures/token.sol'
const sources = {
[contract]: {
content: readFileSync('fixtures/storage.sol', 'utf8'),
},
}
const out = await compile(sources, options)
assert(out.contracts[contract].Storage.abi != null)
assert(out.contracts[contract].Storage.evm.bytecode != null)
})
const out = await compile(sources, options)
assert(out.contracts[contract].Storage.abi != null)
assert(out.contracts[contract].Storage.evm.bytecode != null)
})
}
test('check Err output', async () => {
const sources = {
bad: {
content: readFileSync('fixtures/storage_bad.sol', 'utf8'),
},
}
const sources = {
bad: {
content: readFileSync('fixtures/storage_bad.sol', 'utf8'),
},
}
const out = await compile(sources)
assert(
out?.errors?.[0].message.includes(
'SPDX license identifier not provided in source file'
)
const out = await compile(sources)
assert(
out?.errors?.[0].message.includes(
'SPDX license identifier not provided in source file'
)
assert(
out?.errors?.[1].message.includes(
'Source file does not specify required compiler version'
)
)
assert(
out?.errors?.[1].message.includes(
'Source file does not specify required compiler version'
)
)
})
test('check Err from stderr', async () => {
const sources = {
bad: {
content: readFileSync('fixtures/bad_pragma.sol', 'utf8'),
},
}
const sources = {
bad: {
content: readFileSync('fixtures/bad_pragma.sol', 'utf8'),
},
}
try {
await compile(sources)
assert(false, 'Expected error')
} catch (error) {
assert(
String(error).includes(
'Source file requires different compiler version'
)
)
}
try {
await compile(sources)
assert(false, 'Expected error')
} catch (error) {
assert(
String(error).includes('Source file requires different compiler version')
)
}
})
test('resolve import', () => {
const cases = [
// local
{
file: './fixtures/storage.sol',
expected: resolve('fixtures/storage.sol'),
},
// scopped module with version
{
file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol',
expected: resolve(
'node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol'
),
},
// scopped module without version
{
file: '@openzeppelin/contracts/token/ERC20/ERC20.sol',
expected: resolve(
'node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol'
),
},
// scopped module with wrong version
{
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`,
},
// module without version
{
file: 'acorn/package.json',
expected: resolve('node_modules/acorn/package.json'),
},
// scopped module with version
{
file: 'acorn@8.14.0/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`,
},
]
const cases = [
// local
{
file: './fixtures/storage.sol',
expected: resolve('fixtures/storage.sol'),
},
// scopped module with version
{
file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol',
expected: require.resolve(
'@openzeppelin/contracts/token/ERC20/ERC20.sol'
),
},
// scopped module without version
{
file: '@openzeppelin/contracts/token/ERC20/ERC20.sol',
expected: require.resolve(
'@openzeppelin/contracts/token/ERC20/ERC20.sol'
),
},
// scopped module with wrong version
{
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`,
},
// module without version
{
file: '@openzeppelin/contracts/package.json',
expected: require.resolve('@openzeppelin/contracts/package.json'),
},
// scopped module with version
{
file: '@openzeppelin/contracts@5.1.0/package.json',
expected: require.resolve('@openzeppelin/contracts/package.json'),
},
]
for (const { file, expected } of cases) {
try {
const resolved = tryResolveImport(file)
assert(
resolved === expected,
`\nGot:\n${resolved}\nExpected:\n${expected}`
)
} catch (error) {
assert(
String(error) == expected,
`\nGot:\n${String(error)}\nExpected:\n${expected}`
)
}
for (const { file, expected } of cases) {
try {
const resolved = tryResolveImport(file)
assert(
resolved === expected,
`\nGot:\n${resolved}\nExpected:\n${expected}`
)
} catch (error) {
assert(
String(error) == expected,
`\nGot:\n${String(error)}\nExpected:\n${expected}`
)
}
}
})
+164 -153
View File
@@ -5,114 +5,127 @@ import path from 'path'
import { existsSync, readFileSync } from 'fs'
export type SolcInput = {
[contractName: string]: {
content: string
}
[contractName: string]: {
content: string
}
}
export type SolcError = {
component: string
errorCode: string
formattedMessage: string
message: string
severity: string
sourceLocation?: {
file: string
start: number
end: number
}
type: string
component: string
errorCode: string
formattedMessage: string
message: string
severity: string
sourceLocation?: {
file: string
start: number
end: number
}
type: string
}
export type SolcOutput = {
contracts: {
[contractPath: string]: {
[contractName: string]: {
abi: Array<{
name: string
inputs: Array<{ name: string; type: string }>
outputs: Array<{ name: string; type: string }>
stateMutability: string
type: string
}>
evm: {
bytecode: { object: string }
}
}
contracts: {
[contractPath: string]: {
[contractName: string]: {
abi: Array<{
name: string
inputs: Array<{ name: string; type: string }>
outputs: Array<{ name: string; type: string }>
stateMutability: string
type: string
}>
evm: {
bytecode: { object: string }
}
}
}
errors?: Array<SolcError>
}
errors?: Array<SolcError>
}
export function resolveInputs(sources: SolcInput): SolcInput {
const input = {
language: 'Solidity',
sources,
settings: {
outputSelection: {
'*': {
'*': ['evm.bytecode.object'],
},
},
const input = {
language: 'Solidity',
sources,
settings: {
outputSelection: {
'*': {
'*': ['evm.bytecode.object'],
},
}
},
},
}
const out = solc.compile(JSON.stringify(input), {
import: (path: string) => {
return {
contents: readFileSync(tryResolveImport(path), 'utf8'),
}
const out = solc.compile(JSON.stringify(input), {
import: (path: string) => {
return {
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 {
const v = resolcVersion()
return v.split(' ').pop() ?? v
const v = resolcVersion()
return v.split(' ').pop() ?? v
}
export async function compile(
sources: SolcInput,
option: { bin?: string } = {}
sources: SolcInput,
option: {
optimizer?: Record<string, unknown>
bin?: string
} = {}
): Promise<SolcOutput> {
const input = JSON.stringify({
language: 'Solidity',
sources: resolveInputs(sources),
settings: {
optimizer: { enabled: true, runs: 200 },
outputSelection: {
'*': {
'*': ['abi'],
},
},
const {
optimizer = {
mode: 'z',
fallback_to_optimizing_for_size: true,
enabled: true,
runs: 200,
},
bin,
} = option
const input = JSON.stringify({
language: 'Solidity',
sources: resolveInputs(sources),
settings: {
optimizer,
outputSelection: {
'*': {
'*': ['abi'],
},
})
},
},
})
if (option.bin) {
return compileWithBin(input, option.bin)
}
if (bin) {
return compileWithBin(input, bin)
}
return resolc(input)
return resolc(input)
}
/**
@@ -120,83 +133,81 @@ export async function compile(
* @param importPath - The import path to resolve.
*/
export function tryResolveImport(importPath: string) {
// resolve local path
if (existsSync(importPath)) {
return path.resolve(importPath)
// resolve local path
if (existsSync(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 importRegex = /^(@?[^@/]+(?:\/[^@/]+)?)(?:@([^/]+))?(\/.+)$/
const match = importPath.match(importRegex)
const packageRoot = path.dirname(packageJsonPath)
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)
// 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.`)
}
// 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> {
return new Promise((resolve, reject) => {
const process = spawn(bin, ['--standard-json'])
return new Promise((resolve, reject) => {
const process = spawn(bin, ['--standard-json'])
let output = ''
let error = ''
let output = ''
let error = ''
process.stdin.write(input)
process.stdin.end()
process.stdin.write(input)
process.stdin.end()
process.stdout.on('data', (data) => {
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.stdout.on('data', (data) => {
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}`))
}
})
})
}
+17 -17
View File
@@ -3,28 +3,28 @@ import Resolc from './resolc/resolc'
import type { SolcOutput } from '.'
export function resolc(input: string): SolcOutput {
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson
m.writeToStdin(input)
m.callMain(['--standard-json'])
const err = m.readFromStderr()
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson
m.writeToStdin(input)
m.callMain(['--standard-json'])
const err = m.readFromStderr()
if (err) {
throw new Error(err)
}
if (err) {
throw new Error(err)
}
return JSON.parse(m.readFromStdout()) as SolcOutput
return JSON.parse(m.readFromStdout()) as SolcOutput
}
export function version(): string {
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson
m.callMain(['--version'])
const err = m.readFromStderr()
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson
m.callMain(['--version'])
const err = m.readFromStderr()
if (err) {
throw new Error(err)
}
if (err) {
throw new Error(err)
}
return m.readFromStdout()
return m.readFromStdout()
}
+82 -82
View File
@@ -1,95 +1,95 @@
declare module 'solc/soljson' {}
declare module 'solc' {
// Basic types for input/output handling
export interface CompileInput {
language: string
sources: {
[fileName: string]: {
content: string
}
// Basic types for input/output handling
export interface CompileInput {
language: string
sources: {
[fileName: string]: {
content: string
}
}
settings?: {
optimizer?: {
enabled: boolean
runs: number
}
outputSelection: {
[fileName: string]: {
[contractName: string]: string[]
}
settings?: {
optimizer?: {
enabled: boolean
runs: number
}
outputSelection: {
}
}
}
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]: {
[contractName: string]: string[]
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
}
}
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
}>
}
}
}
}
deployedBytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
}
}
}
}
}
// Main exported functions
export function compile(
input: string | CompileInput,
options?: {
import: (path: string) =>
| {
contents: string
error?: undefined
}
| {
error: string
contents?: undefined
}
}
): 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,
"useUnknownInCatchVariables": true,
"types": ["node"],
"typeRoots": ["./node_modules/@types", "./src"],
"typeRoots": ["../../node_modules/@types", "./src"],
"paths": {
"#src/*": ["./src/*"]
},
-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
-142
View File
@@ -1,142 +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");
});
});
+6618
View File
File diff suppressed because it is too large Load Diff
+11 -6
View File
@@ -3,14 +3,19 @@
"private": true,
"scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"test:wasm": "npm run test:node -w js/wasm",
"build:package": "npm run build:package -w js/wasm",
"lint": "npx eslint 'js/**/*.{cjs,mjs,ts}' && npx prettier --check '**/*.{ts,json,yml,md}'",
"lint:fix": "npx prettier --write '**/*.{mjs,cjs,ts,json,yml,md}'"
"test:wasm": "npm run test:node -w js/emscripten",
"build:package": "npm run build:package -w js/emscripten",
"lint": "npx eslint 'js/**/*.{cjs,mjs,ts}' && npx prettier --check '**/*.{mjs,cjs,ts}'",
"lint:fix": "npx prettier --write '**/*.{mjs,cjs,ts}'"
},
"workspaces": [
"crates/solidity/src/tests/cli-tests",
"js/wasm",
"js/emscripten",
"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