Compare commits

...

9 Commits

Author SHA1 Message Date
pgherveou 20a3b30296 wip 2025-04-28 16:39:59 +02:00
pgherveou f44b2485bd lint 2025-04-24 16:34:06 +02:00
xermicus f6a412eef4 release resolc v0.1.0-dev.14 (#289)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-24 11:57:46 +02:00
xermicus 20e77cb0b5 configurable stack and heap memory size (#288)
- Allow configuration of the maximum heap and stack size via CLI flags
and JSON input settings.
- Increase the default value for the stack size to 32kb.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-24 10:47:38 +02:00
xermicus 357bf58868 allow dynamic configuration of the heap memory (#287)
This PR changes the implementation of the emulated EVM heap memory:
Instead of linking in a C implementation the code is emitted directly
into the contract module. Which allows making it configurable via a
compiler parameter (a follow up PR to this).

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-23 20:25:55 +02:00
xermicus f937188991 revive-runner: install with locked dependencies (#286)
Make the installation of  `revive-runner` easier:
- Use locked dependencies to avoid issues with downstream crates
- Make the llvm-context crate an optional dependency to the runner
- Add it to the default `test` target ensuring that this actually works

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-22 19:46:18 +02:00
xermicus 6e44488b4f revive-runner: add a utility binary for local contract execution (#284)
I had this in mind for a while but never implemented a standalone binary
so far because I always end up writing an integration test anyways.

However, using a standalone version of the pallet based on the
revive-runner crate is something people filing in bug reports do
anyways, for example:
https://github.com/paritytech/revive/issues/266
https://github.com/paritytech/contract-issues/issues/54
---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-22 15:34:51 +02:00
xermicus 5003f3e9ac llvm-context: alloca at the function entry if possible (#283)
Closes  #48

Change the code size test to no longer emit debug info as to get a more
accurate picture.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-15 15:22:24 +02:00
xermicus 431b5a2ce5 llvm-context: lazy handling of function arguments and immutable data (#282)
- Lazily load function arguments so that they can be passed as pointers.
- Lazily call the immutable store function to avoid storing zero sized
immutable data.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-14 15:54:59 +02:00
108 changed files with 6266 additions and 1031 deletions
+18 -19
View File
@@ -1,19 +1,18 @@
name: "Get Emscripten SDK" name: "Get Emscripten SDK"
inputs: inputs:
version: version:
description: "" description: ""
required: false required: false
default: "3.1.64" default: "3.1.64"
runs:
runs: using: "composite"
using: "composite" steps:
steps: - name: install emsdk
- name: install emsdk shell: bash
shell: bash run: |
run: | git clone https://github.com/emscripten-core/emsdk.git ./emsdk/
git clone https://github.com/emscripten-core/emsdk.git ./emsdk/ cd emsdk
cd emsdk git checkout tags/${{ inputs.version }}
git checkout tags/${{ inputs.version }} ./emsdk install ${{ inputs.version }}
./emsdk install ${{ inputs.version }} ./emsdk activate ${{ inputs.version }}
./emsdk activate ${{ inputs.version }}
+70 -70
View File
@@ -1,70 +1,70 @@
# example: # example:
# - uses: ./.github/actions/get-llvm # - uses: ./.github/actions/get-llvm
# with: # with:
# target: x86_64-unknown-linux-gnu # target: x86_64-unknown-linux-gnu
name: "Download LLVM" name: "Download LLVM"
inputs: inputs:
target: target:
required: true required: true
runs: runs:
using: "composite" using: "composite"
steps: steps:
- name: find asset - name: find asset
id: find id: find
uses: actions/github-script@v7 uses: actions/github-script@v7
env: env:
target: ${{ inputs.target }} target: ${{ inputs.target }}
with: with:
result-encoding: string result-encoding: string
script: | script: |
let page = 1; let page = 1;
let releases = []; let releases = [];
let releasePrefix = "llvm-" let releasePrefix = "llvm-"
let target = process.env.target let target = process.env.target
do { do {
const res = await github.rest.repos.listReleases({ const res = await github.rest.repos.listReleases({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
per_page: 50, per_page: 50,
page, page,
}); });
releases = res.data releases = res.data
releases.sort((a, b) => { releases.sort((a, b) => {
return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0); return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0);
}); });
let llvmLatestRelease = releases.find(release => { let llvmLatestRelease = releases.find(release => {
return release.tag_name.startsWith(releasePrefix); return release.tag_name.startsWith(releasePrefix);
}); });
if (llvmLatestRelease){ if (llvmLatestRelease){
let asset = llvmLatestRelease.assets.find(asset =>{ let asset = llvmLatestRelease.assets.find(asset =>{
return asset.name.includes(target); return asset.name.includes(target);
}); });
if (!asset){ if (!asset){
core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`); core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`);
process.exit(); process.exit();
} }
return asset.browser_download_url; return asset.browser_download_url;
} }
page++; page++;
} while(releases.length > 0); } while(releases.length > 0);
core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`); core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`);
process.exit(); process.exit();
- name: download - name: download
shell: bash shell: bash
run: | run: |
curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }} curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }}
- name: unpack - name: unpack
shell: bash shell: bash
run: | run: |
tar -xf llvm.tar.gz tar -xf llvm.tar.gz
rm llvm.tar.gz rm llvm.tar.gz
+49
View File
@@ -0,0 +1,49 @@
name: NPM Release
on:
release:
types: [released]
env:
CI: true
jobs:
publish:
name: Build & Publish to NPM
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Install
run: yarn install --immutable
- name: update-resolc
run: yarn update-resolc
- name: Build
run: yarn build
- name: Set version
run: npm version --no-git-tag-version ${{github.event.release.tag_name}}
- name: npm pack
run: npm pack
- uses: actions/upload-artifact@v4
with:
name: package
path: 'parity-revive-*.tgz'
- uses: octokit/request-action@bbedc70b1981e610d89f1f8de88311a1fc02fb83
with:
route: POST /repos/paritytech/npm_publish_automation/actions/workflows/publish.yml/dispatches
ref: main
inputs: '${{ format(''{{ "repo": "{0}", "run_id": "{1}" }}'', github.repository, github.run_id) }}'
env:
GITHUB_TOKEN: ${{ secrets.NPM_PUBLISH_AUTOMATION_TOKEN }}
+9 -1
View File
@@ -37,7 +37,15 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
target: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, wasm32-unknown-emscripten, aarch64-apple-darwin, x86_64-apple-darwin, x86_64-pc-windows-msvc] target:
[
x86_64-unknown-linux-gnu,
x86_64-unknown-linux-musl,
wasm32-unknown-emscripten,
aarch64-apple-darwin,
x86_64-apple-darwin,
x86_64-pc-windows-msvc,
]
include: include:
- target: x86_64-unknown-linux-gnu - target: x86_64-unknown-linux-gnu
builder-arg: gnu builder-arg: gnu
+3 -3
View File
@@ -4,9 +4,9 @@ on:
branches: ["main"] branches: ["main"]
types: [opened, synchronize] types: [opened, synchronize]
paths: paths:
- 'LLVM.lock' - "LLVM.lock"
- 'crates/llvm-builder/**' - "crates/llvm-builder/**"
- '.github/workflows/test-llvm-builder.yml' - ".github/workflows/test-llvm-builder.yml"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+1
View File
@@ -1,4 +1,5 @@
/target /target
/js/resolc/dist
target-llvm target-llvm
*.dot *.dot
.vscode/ .vscode/
+52 -2
View File
@@ -4,6 +4,33 @@
This is a development pre-release. This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
### Changed
### Fixed
## v0.1.0-dev.14
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
- The `revive-runner` helper utility binary which helps to run contracts locally without a blockchain node.
- Allow configuration of the EVM heap memory size and stack size via CLI flags and JSON input settings.
### Changed
- The default PVM stack memory size was increased from 16kb to 32kb.
### Fixed
- Constructors avoid storing zero sized immutable data on exit.
## v0.1.0-dev.13 ## v0.1.0-dev.13
This is a development pre-release. This is a development pre-release.
@@ -11,16 +38,19 @@ This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added ### Added
- Support for solc v0.8.29 - Support for solc v0.8.29
- Decouples the solc JSON-input-output type definitions from the Solidity fronted and expose them via a dedicated crate. - Decouples the solc JSON-input-output type definitions from the Solidity fronted and expose them via a dedicated crate.
- `--supported-solc-versions` for `resolc` binary to return a `semver` range of supported `solc` versions. - `--supported-solc-versions` for `resolc` binary to return a `semver` range of supported `solc` versions.
- Support for passing LLVM command line options via the prcoess input or providing one or more `--llvm-arg='..'` resolc CLI flag. This allows more fine-grained control over the LLVM backend configuration. - Support for passing LLVM command line options via the prcoess input or providing one or more `--llvm-arg='..'` resolc CLI flag. This allows more fine-grained control over the LLVM backend configuration.
### Changed ### Changed
- Storage keys and values are big endian. This was a pre-mature optimization because for the contract itself it this is a no-op and thus not observable. However we should consider the storage layout as part of the contract ABI. The endianness of transient storage values are still kept as-is. - Storage keys and values are big endian. This was a pre-mature optimization because for the contract itself it this is a no-op and thus not observable. However we should consider the storage layout as part of the contract ABI. The endianness of transient storage values are still kept as-is.
- Running `resolc` using webkit is no longer supported. - Running `resolc` using webkit is no longer supported.
### Fixed ### Fixed
- A missing byte swap for the create2 salt value. - A missing byte swap for the create2 salt value.
## v0.1.0-dev.12 ## v0.1.0-dev.12
@@ -30,10 +60,12 @@ This is a development pre-release.
Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9` Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9`
### Added ### Added
- Per file output selection for `--standard-json` mode. - Per file output selection for `--standard-json` mode.
- The `ir` output selection option for `--standard-json` mode. - The `ir` output selection option for `--standard-json` mode.
### Changed ### Changed
- Improved code size: Large contracts compile to smaller code blobs when enabling aggressive size optimizations (`-Oz`). - Improved code size: Large contracts compile to smaller code blobs when enabling aggressive size optimizations (`-Oz`).
### Fixed ### Fixed
@@ -49,6 +81,7 @@ Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Changed ### Changed
### Fixed ### Fixed
- A bug causing incorrect loads from the emulated EVM linear memory. - A bug causing incorrect loads from the emulated EVM linear memory.
- A missing integer truncate after switching to 64bit. - A missing integer truncate after switching to 64bit.
@@ -59,10 +92,12 @@ This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50` Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added ### Added
- Support for the `coinbase` opcode. - Support for the `coinbase` opcode.
- The resolc web JS version. - The resolc web JS version.
### Changed ### Changed
- Missing the `--overwrite` flag emits an error instead of a warning. - Missing the `--overwrite` flag emits an error instead of a warning.
- The `resolc` executable prints the help by default. - The `resolc` executable prints the help by default.
- Removed support for legacy EVM assembly (EVMLA) translation. - Removed support for legacy EVM assembly (EVMLA) translation.
@@ -72,6 +107,7 @@ Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
If detected, the re-entrant call flag is not set and 0 deposit limit is endowed. If detected, the re-entrant call flag is not set and 0 deposit limit is endowed.
### Fixed ### Fixed
- Solidity: Add the solc `--libraries` files to sources. - Solidity: Add the solc `--libraries` files to sources.
- A data race in tests. - A data race in tests.
- Fix `broken pipe` errors. - Fix `broken pipe` errors.
@@ -85,9 +121,11 @@ This is a development pre-release.
### Added ### Added
### Changed ### Changed
- Syscalls with more than 6 arguments now pack them into registers. - Syscalls with more than 6 arguments now pack them into registers.
### Fixed ### Fixed
- Remove reloading of the resolc.js file (fix issue with relative path in web worker) - Remove reloading of the resolc.js file (fix issue with relative path in web worker)
## v0.1.0-dev.8 ## v0.1.0-dev.8
@@ -95,15 +133,18 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
### Added ### Added
- The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency. - The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency.
- Initial support for running `resolc` in the browser. - Initial support for running `resolc` in the browser.
### Changed ### Changed
- Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`. - Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`.
- The minimum supported Rust version is `1.81.0`. - The minimum supported Rust version is `1.81.0`.
- Error out early instead of invoking `solc` with invalid base or include path flags. - Error out early instead of invoking `solc` with invalid base or include path flags.
### Fixed ### Fixed
- Decouple the LLVM target dependency from the LLVM host dependency. - Decouple the LLVM target dependency from the LLVM host dependency.
- Do not error out if no files and no errors were produced. This aligns resolc closer to solc. - Do not error out if no files and no errors were produced. This aligns resolc closer to solc.
- Fixes input normalization in the Wasm version. - Fixes input normalization in the Wasm version.
@@ -113,17 +154,20 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
### Added ### Added
- Implement the `GASPRICE` opcode. - Implement the `GASPRICE` opcode.
- Implement the `BASEFEE` opcode. - Implement the `BASEFEE` opcode.
- Implement the `GASLIMIT` opcode. - Implement the `GASLIMIT` opcode.
### Changed ### Changed
- The `GAS` opcode now returns the remaining `ref_time`. - The `GAS` opcode now returns the remaining `ref_time`.
- Contracts can now be supplied call data input of arbitrary size. - Contracts can now be supplied call data input of arbitrary size.
- Some syscalls now return the value in a register, slightly improving emitted contract code. - Some syscalls now return the value in a register, slightly improving emitted contract code.
- Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit. - Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit.
### Fixed ### Fixed
- A linker bug which was preventing certain contracts from linking with the PVM linker. - A linker bug which was preventing certain contracts from linking with the PVM linker.
- JS: Fix encoding conversion from JS string (UTF-16) to UTF-8. - JS: Fix encoding conversion from JS string (UTF-16) to UTF-8.
- The git commit hash slug is always displayed in the version string. - The git commit hash slug is always displayed in the version string.
@@ -133,6 +177,7 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
# Added # Added
- Implement the `BLOCKHASH` opcode. - Implement the `BLOCKHASH` opcode.
- Implement delegate calls. - Implement delegate calls.
- Implement the `GASPRICE` opcode. Currently hard-coded to return `1`. - Implement the `GASPRICE` opcode. Currently hard-coded to return `1`.
@@ -140,21 +185,24 @@ This is a development pre-release.
- Initial support for emitting debug info (opt in via the `-g` flag) - Initial support for emitting debug info (opt in via the `-g` flag)
# Changed # Changed
- resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time. - resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time.
- The RISC-V bit-manipulation target feature (`zbb`) is enabled. - The RISC-V bit-manipulation target feature (`zbb`) is enabled.
# Fixed # Fixed
- Compilation to Wasm (for usage in node and web browsers)
- Compilation to Wasm (for usage in node and web browsers)
## v0.1.0-dev.5 ## v0.1.0-dev.5
This is development pre-release. This is development pre-release.
# Added # Added
- Implement the `CODESIZE` and `EXTCODESIZE` opcodes. - Implement the `CODESIZE` and `EXTCODESIZE` opcodes.
# Changed # Changed
- Include the full revive version in the contract metadata. - Include the full revive version in the contract metadata.
# Fixed # Fixed
@@ -164,9 +212,11 @@ This is development pre-release.
This is development pre-release. This is development pre-release.
# Added # Added
- Support the `ORIGIN` opcode. - Support the `ORIGIN` opcode.
# Changed # Changed
- Update polkavm to `v0.14.0`. - Update polkavm to `v0.14.0`.
- Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts. - Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts.
Generated
+18 -15
View File
@@ -4553,7 +4553,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]] [[package]]
name = "lld-sys" name = "lld-sys"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -8266,7 +8266,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-benchmarks" name = "revive-benchmarks"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"criterion", "criterion",
@@ -8278,18 +8278,18 @@ dependencies = [
[[package]] [[package]]
name = "revive-build-utils" name = "revive-build-utils"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
[[package]] [[package]]
name = "revive-builtins" name = "revive-builtins"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"revive-build-utils", "revive-build-utils",
] ]
[[package]] [[package]]
name = "revive-common" name = "revive-common"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde", "serde",
@@ -8299,7 +8299,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-differential" name = "revive-differential"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-genesis", "alloy-genesis",
"alloy-primitives", "alloy-primitives",
@@ -8312,7 +8312,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-integration" name = "revive-integration"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
@@ -8328,7 +8328,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-linker" name = "revive-linker"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"libc", "libc",
@@ -8340,7 +8340,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-llvm-builder" name = "revive-llvm-builder"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@@ -8362,7 +8362,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-llvm-context" name = "revive-llvm-context"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hex", "hex",
@@ -8384,9 +8384,12 @@ dependencies = [
[[package]] [[package]]
name = "revive-runner" name = "revive-runner"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"anyhow",
"clap",
"env_logger 0.11.6",
"hex", "hex",
"parity-scale-codec", "parity-scale-codec",
"polkadot-sdk 0.1.0", "polkadot-sdk 0.1.0",
@@ -8400,7 +8403,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-runtime-api" name = "revive-runtime-api"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"inkwell", "inkwell",
@@ -8410,7 +8413,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-solc-json-interface" name = "revive-solc-json-interface"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"rayon", "rayon",
@@ -8422,7 +8425,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-solidity" name = "revive-solidity"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -8449,7 +8452,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-stdlib" name = "revive-stdlib"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"inkwell", "inkwell",
"revive-build-utils", "revive-build-utils",
+15 -15
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [workspace.package]
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
authors = [ authors = [
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>", "Parity Technologies <admin@parity.io>",
@@ -14,20 +14,20 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0" rust-version = "1.81.0"
[workspace.dependencies] [workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.13", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0-dev.14", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.13", path = "crates/builtins" } revive-builtins = { version = "0.1.0-dev.14", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.13", path = "crates/common" } revive-common = { version = "0.1.0-dev.14", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.13", path = "crates/differential" } revive-differential = { version = "0.1.0-dev.14", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.13", path = "crates/integration" } revive-integration = { version = "0.1.0-dev.14", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.13", path = "crates/linker" } revive-linker = { version = "0.1.0-dev.14", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.13", path = "crates/lld-sys" } lld-sys = { version = "0.1.0-dev.14", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.13", path = "crates/llvm-context" } revive-llvm-context = { version = "0.1.0-dev.14", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.13", path = "crates/runtime-api" } revive-runtime-api = { version = "0.1.0-dev.14", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.13", path = "crates/runner" } revive-runner = { version = "0.1.0-dev.14", path = "crates/runner" }
revive-solc-json-interface = { version = "0.1.0-dev.13", path = "crates/solc-json-interface" } revive-solc-json-interface = { version = "0.1.0-dev.14", path = "crates/solc-json-interface" }
revive-solidity = { version = "0.1.0-dev.13", path = "crates/solidity" } revive-solidity = { version = "0.1.0-dev.14", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.13", path = "crates/stdlib" } revive-stdlib = { version = "0.1.0-dev.14", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.13", path = "crates/build-utils" } revive-build-utils = { version = "0.1.0-dev.14", path = "crates/build-utils" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.2" cc = "1.2"
+5 -1
View File
@@ -5,6 +5,7 @@
install-wasm \ install-wasm \
install-llvm-builder \ install-llvm-builder \
install-llvm \ install-llvm \
install-revive-runner \
format \ format \
clippy \ clippy \
machete \ machete \
@@ -39,6 +40,9 @@ install-llvm: install-llvm-builder
revive-llvm clone revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang revive-llvm build --llvm-projects lld --llvm-projects clang
install-revive-runner:
cargo install --path crates/runner --no-default-features --locked
format: format:
cargo fmt --all --check cargo fmt --all --check
@@ -49,7 +53,7 @@ machete:
cargo install cargo-machete cargo install cargo-machete
cargo machete cargo machete
test: format clippy machete test-cli test-workspace test: format clippy machete test-cli test-workspace install-revive-runner
test-integration: install-bin test-integration: install-bin
cargo test --package revive-integration cargo test --package revive-integration
+7 -3
View File
@@ -14,6 +14,7 @@ This is experimental software in active development and not ready just yet for p
Discussion around the development is hosted on the [Polkadot Forum](https://forum.polkadot.network/t/contracts-update-solidity-on-polkavm/6949#a-new-solidity-compiler-1). Discussion around the development is hosted on the [Polkadot Forum](https://forum.polkadot.network/t/contracts-update-solidity-on-polkavm/6949#a-new-solidity-compiler-1).
## Installation ## Installation
Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions. Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions.
## Building from source ## Building from source
@@ -30,11 +31,13 @@ Building revive requires a [stable Rust installation](https://rustup.rs/) and a
Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases. Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases.
> **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive: > **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive:
>
> ```sh > ```sh
> xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/* > xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/*
> ``` > ```
After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it: After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it:
```sh ```sh
export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final
``` ```
@@ -44,7 +47,7 @@ export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/targ
<details> <details>
<summary>Building from source</summary> <summary>Building from source</summary>
Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards. Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards.
The `Makefile` provides a shortcut target to obtain a compatible LLVM build: The `Makefile` provides a shortcut target to obtain a compatible LLVM build:
@@ -93,11 +96,12 @@ make test-wasm
## Development ## Development
Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks. Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks.
Ensure that your branch passes `make test` locally when submitting a pull request. Ensure that your branch passes `make test` locally when submitting a pull request.
### Design overview ### Design overview
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
[Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc` (the project started as a fork of the era compiler). [Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc` (the project started as a fork of the era compiler).
+1 -1
View File
@@ -18,4 +18,4 @@ To create a new pre-release:
To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`. To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`.
Version suffix will be resolved automatically. Version suffix will be resolved automatically.
The workflows will create new GitHub release, and upload LLVM binaries. The workflows will create new GitHub release, and upload LLVM binaries.
Next release of resolc will use newly created binaries. Next release of resolc will use newly created binaries.
+1 -1
View File
@@ -1 +1 @@
https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md
+42 -42
View File
@@ -3,71 +3,71 @@
## Table of Contents ## Table of Contents
- [Benchmark Results](#benchmark-results) - [Benchmark Results](#benchmark-results)
- [Baseline](#baseline) - [Baseline](#baseline)
- [OddPorduct](#oddporduct) - [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber) - [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive) - [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative) - [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet) - [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1) - [SHA1](#sha1)
## Benchmark Results ## Benchmark Results
### Baseline ### Baseline
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:--------|:------------------------|:-------------------------------- | | :------ | :------------------------ | :------------------------------- |
| **`0`** | `3.36 us` (✅ **1.00x**) | `11.84 us` (*3.52x slower*) | | **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (**1.02x slower**) |
### OddPorduct ### OddPorduct
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- | | :----------- | :------------------------- | :------------------------------- |
| **`10000`** | `3.11 ms` (✅ **1.00x**) | `1.53 ms` (🚀 **2.03x faster**) | | **`10000`** | `3.60 ms` (✅ **1.00x**) | `1.57 ms` (🚀 **2.28x faster**) |
| **`100000`** | `30.70 ms` (✅ **1.00x**) | `15.54 ms` (🚀 **1.98x faster**) | | **`100000`** | `34.72 ms` (✅ **1.00x**) | `14.82 ms` (🚀 **2.34x faster**) |
| **`300000`** | `92.68 ms` (✅ **1.00x**) | `45.47 ms` (🚀 **2.04x faster**) | | **`300000`** | `105.01 ms` (✅ **1.00x**) | `44.11 ms` (🚀 **2.38x faster**) |
### TriangleNumber ### TriangleNumber
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- | | :----------- | :------------------------ | :------------------------------- |
| **`10000`** | `2.29 ms` (✅ **1.00x**) | `1.09 ms` (🚀 **2.11x faster**) | | **`10000`** | `2.43 ms` (✅ **1.00x**) | `1.12 ms` (🚀 **2.17x faster**) |
| **`100000`** | `22.84 ms` (✅ **1.00x**) | `10.66 ms` (🚀 **2.14x faster**) | | **`100000`** | `24.20 ms` (✅ **1.00x**) | `10.86 ms` (🚀 **2.23x faster**) |
| **`360000`** | `82.29 ms` (✅ **1.00x**) | `37.01 ms` (🚀 **2.22x faster**) | | **`360000`** | `88.69 ms` (✅ **1.00x**) | `38.46 ms` (🚀 **2.31x faster**) |
### FibonacciRecursive ### FibonacciRecursive
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:---------|:--------------------------|:--------------------------------- | | :------- | :------------------------- | :-------------------------------- |
| **`12`** | `135.67 us` (✅ **1.00x**) | `125.02 us` (✅ **1.09x faster**) | | **`12`** | `144.17 us` (✅ **1.00x**) | `150.85 us` (✅ **1.05x slower**) |
| **`16`** | `903.75 us` (✅ **1.00x**) | `762.79 us` (✅ **1.18x faster**) | | **`16`** | `938.71 us` (✅ **1.00x**) | `922.11 us` (✅ **1.02x faster**) |
| **`20`** | `6.12 ms` (✅ **1.00x**) | `4.96 ms` (✅ **1.23x faster**) | | **`20`** | `6.54 ms` (✅ **1.00x**) | `6.20 ms` (✅ **1.05x faster**) |
| **`24`** | `42.05 ms` (✅ **1.00x**) | `33.86 ms` (✅ **1.24x faster**) | | **`24`** | `45.73 ms` (✅ **1.00x**) | `41.98 ms` (✅ **1.09x faster**) |
### FibonacciIterative ### FibonacciIterative
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- | | :-------- | :------------------------ | :------------------------------- |
| **`64`** | `15.04 us` (✅ **1.00x**) | `29.45 us` (❌ *1.96x slower*) | | **`64`** | `23.00 us` (✅ **1.00x**) | `31.88 us` (❌ _1.39x slower_) |
| **`128`** | `26.36 us` (✅ **1.00x**) | `42.19 us` (❌ *1.60x slower*) | | **`128`** | `35.28 us` (✅ **1.00x**) | `42.43 us` (❌ _1.20x slower_) |
| **`256`** | `48.61 us` (✅ **1.00x**) | `65.71 us` (*1.35x slower*) | | **`256`** | `60.12 us` (✅ **1.00x**) | `61.20 us` (**1.02x slower**) |
### FibonacciBinet ### FibonacciBinet
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- | | :-------- | :------------------------ | :----------------------------- |
| **`64`** | `15.22 us` (✅ **1.00x**) | `41.46 us` (❌ *2.72x slower*) | | **`64`** | `23.01 us` (✅ **1.00x**) | `47.74 us` (❌ _2.07x slower_) |
| **`128`** | `17.05 us` (✅ **1.00x**) | `42.84 us` (❌ *2.51x slower*) | | **`128`** | `25.44 us` (✅ **1.00x**) | `49.67 us` (❌ _1.95x slower_) |
| **`256`** | `19.00 us` (✅ **1.00x**) | `44.36 us` (❌ *2.34x slower*) | | **`256`** | `28.66 us` (✅ **1.00x**) | `53.01 us` (❌ _1.85x slower_) |
### SHA1 ### SHA1
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:--------------------------|:--------------------------------- | | :-------- | :------------------------- | :------------------------------ |
| **`1`** | `110.04 us` (✅ **1.00x**) | `216.11 us` (❌ *1.96x slower*) | | **`1`** | `135.87 us` (✅ **1.00x**) | `243.75 us` (❌ _1.79x slower_) |
| **`64`** | `209.04 us` (✅ **1.00x**) | `309.48 us` (❌ *1.48x slower*) | | **`64`** | `258.45 us` (✅ **1.00x**) | `355.70 us` (❌ _1.38x slower_) |
| **`512`** | `903.65 us` (✅ **1.00x**) | `980.49 us` (✅ **1.09x slower**) | | **`512`** | `1.10 ms` (✅ **1.00x**) | `1.09 ms` (✅ **1.01x faster**) |
--- ---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+45 -45
View File
@@ -1,47 +1,47 @@
{ {
"config": { "config": {
"chainId": 420420420, "chainId": 420420420,
"homesteadBlock": 0, "homesteadBlock": 0,
"eip150Block": 0, "eip150Block": 0,
"eip155Block": 0, "eip155Block": 0,
"eip158Block": 0, "eip158Block": 0,
"byzantiumBlock": 0, "byzantiumBlock": 0,
"constantinopleBlock": 0, "constantinopleBlock": 0,
"petersburgBlock": 0, "petersburgBlock": 0,
"istanbulBlock": 0, "istanbulBlock": 0,
"berlinBlock": 0, "berlinBlock": 0,
"londonBlock": 0, "londonBlock": 0,
"arrowGlacierBlock": 0, "arrowGlacierBlock": 0,
"grayGlacierBlock": 0, "grayGlacierBlock": 0,
"shanghaiTime": 0, "shanghaiTime": 0,
"cancunTime": 0, "cancunTime": 0,
"terminalTotalDifficulty": 0, "terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true, "terminalTotalDifficultyPassed": true,
"blobSchedule": { "blobSchedule": {
"cancun": { "cancun": {
"target": 3, "target": 3,
"max": 6, "max": 6,
"baseFeeUpdateFraction": 3338477 "baseFeeUpdateFraction": 3338477
} }
}
},
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
} }
} },
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
}
}
+9 -9
View File
@@ -1,10 +1,10 @@
{ {
"Baseline": 1443, "Baseline": 939,
"Computation": 2788, "Computation": 2282,
"DivisionArithmetics": 9748, "DivisionArithmetics": 8849,
"ERC20": 19150, "ERC20": 18308,
"Events": 2201, "Events": 1640,
"FibonacciIterative": 2041, "FibonacciIterative": 1497,
"Flipper": 2691, "Flipper": 2099,
"SHA1": 8997 "SHA1": 8243
} }
+64 -55
View File
@@ -6,6 +6,7 @@ Parity fork of the [Matter Labs zksync LLVM builder](https://github.com/matter-l
The LLVM compiler framework for revive must be built with our tool called `revive-llvm`. The LLVM compiler framework for revive must be built with our tool called `revive-llvm`.
This is because the revive compiler has requirements not fullfilled in upstream builds: This is because the revive compiler has requirements not fullfilled in upstream builds:
- Special builds for compiling the frontend into statically linked ELF binaries and also Wasm executables - Special builds for compiling the frontend into statically linked ELF binaries and also Wasm executables
- The RISC-V target (the PolkaVM target) - The RISC-V target (the PolkaVM target)
- The compiler-rt builtins for the PolkaVM target - The compiler-rt builtins for the PolkaVM target
@@ -17,85 +18,92 @@ Obtain a compatible build for your host platform from the release section of thi
<details> <details>
<summary>1. Install the system prerequisites.</summary> <summary>1. Install the system prerequisites.</summary>
* Linux (Debian): - Linux (Debian):
Install the following packages: Install the following packages:
```shell
apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
```
* Linux (Arch):
Install the following packages: ```shell
```shell apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
pacman -Syu which cmake ninja curl git pkg-config clang lld ```
```
* MacOS: - Linux (Arch):
* Install the [HomeBrew](https://brew.sh) package manager. Install the following packages:
* Install the following packages:
```shell ```shell
brew install cmake ninja coreutils pacman -Syu which cmake ninja curl git pkg-config clang lld
``` ```
* Install your choice of a recent LLVM/[Clang](https://clang.llvm.org) compiler, e.g. via [Xcode](https://developer.apple.com/xcode/), [Apples Command Line Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html), or your preferred package manager. - MacOS:
</details>
* Install the [HomeBrew](https://brew.sh) package manager.
* Install the following packages:
```shell
brew install cmake ninja coreutils
```
* Install your choice of a recent LLVM/[Clang](https://clang.llvm.org) compiler, e.g. via [Xcode](https://developer.apple.com/xcode/), [Apples Command Line Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html), or your preferred package manager.
</details>
<details> <details>
<summary>2. Install Rust.</summary> <summary>2. Install Rust.</summary>
* Follow the latest [official instructions](https://www.rust-lang.org/tools/install: - Follow the latest [official instructions](https://www.rust-lang.org/tools/install:
```shell `shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. ${HOME}/.cargo/env . ${HOME}/.cargo/env
``` `
> Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform. > Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform.
</details>
</details>
<details> <details>
<summary>3. Install the revive LLVM framework builder.</summary> <summary>3. Install the revive LLVM framework builder.</summary>
* Install the builder using `cargo`: - Install the builder using `cargo`:
```shell
cargo install --git https://github.com/paritytech/revive-llvm-builder --force --locked
```
> The builder is not the LLVM framework itself, but a tool that clones its repository and runs a sequence of build commands. By default it is installed in `~/.cargo/bin/`, which is recommended to be added to your `$PATH`. ```shell
cargo install --git https://github.com/paritytech/revive-llvm-builder --force --locked
```
> The builder is not the LLVM framework itself, but a tool that clones its repository and runs a sequence of build commands. By default it is installed in `~/.cargo/bin/`, which is recommended to be added to your `$PATH`.
</details> </details>
<details> <details>
<summary>4. (Optional) Create the `LLVM.lock` file.</summary> <summary>4. (Optional) Create the `LLVM.lock` file.</summary>
* The `LLVM.lock` dictates the LLVM source tree being used. - The `LLVM.lock` dictates the LLVM source tree being used.
A default `./LLVM.lock` pointing to the release used for development is already provided. A default `./LLVM.lock` pointing to the release used for development is already provided.
</details> </details>
<details> <details>
<summary>5. Build LLVM.</summary> <summary>5. Build LLVM.</summary>
* Clone and build the LLVM framework using the `revive-llvm` tool. - Clone and build the LLVM framework using the `revive-llvm` tool.
The clang and lld projects are required for the `resolc` Solidity frontend executable; they are enabled by default. LLVM assertions are also enabled by default. The clang and lld projects are required for the `resolc` Solidity frontend executable; they are enabled by default. LLVM assertions are also enabled by default.
```shell ```shell
revive-llvm clone revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang revive-llvm build --llvm-projects lld --llvm-projects clang
``` ```
Build artifacts end up in the `./target-llvm/gnu/target-final/` directory by default. Build artifacts end up in the `./target-llvm/gnu/target-final/` directory by default.
The `gnu` directory depends on the supported archticture and will either be `gnu`, `musl` or `emscripten`. The `gnu` directory depends on the supported archticture and will either be `gnu`, `musl` or `emscripten`.
You now need to export the final target directory `$LLVM_SYS_181_PREFIX`: `export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final` You now need to export the final target directory `$LLVM_SYS_181_PREFIX`: `export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final`
If built with the `--enable-tests` option, test tools will be in the `./target-llvm/gnu/build-final/` directory, along with copies of the build artifacts. For all supported build options, run `revive-llvm build --help`. If built with the `--enable-tests` option, test tools will be in the `./target-llvm/gnu/build-final/` directory, along with copies of the build artifacts. For all supported build options, run `revive-llvm build --help`.
</details> </details>
## Supported target architectures ## Supported target architectures
The following target platforms are supported: The following target platforms are supported:
- Linux GNU (x86) - Linux GNU (x86)
- Linux MUSL (x86) - Linux MUSL (x86)
- MacOS (aarch64) - MacOS (aarch64)
@@ -105,28 +113,29 @@ The following target platforms are supported:
<details> <details>
<summary>Building for MUSL</summary> <summary>Building for MUSL</summary>
* Via a musl build we can build revive into fully static ELF binaries. - Via a musl build we can build revive into fully static ELF binaries.
Which is desirable for reproducible Solidity contracts builds. Which is desirable for reproducible Solidity contracts builds.
The resulting binary is also very portable, akin to the`solc` frontend binary distribution. The resulting binary is also very portable, akin to the`solc` frontend binary distribution.
Clone and build the LLVM framework using the `revive-llvm` tool: Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env musl clone ```shell
revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld revive-llvm --target-env musl clone
``` revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details> </details>
<details> <details>
<summary>Building for Emscripten</summary> <summary>Building for Emscripten</summary>
* Via an emsdk build we can run revive in the browser and on node.js. - Via an emsdk build we can run revive in the browser and on node.js.
Clone and build the LLVM framework using the `revive-llvm` tool: Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env emscripten clone ```shell
revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld revive-llvm --target-env emscripten clone
``` revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details> </details>
+3 -1
View File
@@ -5,6 +5,7 @@ use std::sync::OnceLock;
pub use self::debug_config::ir_type::IRType as DebugConfigIR; pub use self::debug_config::ir_type::IRType as DebugConfigIR;
pub use self::debug_config::DebugConfig; 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::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings; pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer; pub use self::optimizer::Optimizer;
@@ -28,6 +29,7 @@ pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryF
pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction; pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction;
pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction; pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction;
pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction; pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction;
pub use self::polkavm::context::function::runtime::sbrk::Sbrk as PolkaVMSbrkFunction;
pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode; pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode;
pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry; pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry;
pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode; pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode;
@@ -60,7 +62,6 @@ pub use self::polkavm::evm::ext_code as polkavm_evm_ext_code;
pub use self::polkavm::evm::immutable as polkavm_evm_immutable; pub use self::polkavm::evm::immutable as polkavm_evm_immutable;
pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction; pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction;
pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction; pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction;
pub use self::polkavm::evm::math as polkavm_evm_math; pub use self::polkavm::evm::math as polkavm_evm_math;
pub use self::polkavm::evm::memory as polkavm_evm_memory; pub use self::polkavm::evm::memory as polkavm_evm_memory;
pub use self::polkavm::evm::r#return as polkavm_evm_return; pub use self::polkavm::evm::r#return as polkavm_evm_return;
@@ -75,6 +76,7 @@ pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine; pub use self::target_machine::TargetMachine;
pub(crate) mod debug_config; pub(crate) mod debug_config;
pub(crate) mod memory;
pub(crate) mod optimizer; pub(crate) mod optimizer;
pub(crate) mod polkavm; pub(crate) mod polkavm;
pub(crate) mod target_machine; pub(crate) mod target_machine;
+21
View File
@@ -0,0 +1,21 @@
//! The compile time PolkaVM memory configuration settings.
use serde::{Deserialize, Serialize};
/// The PolkaVM memory configuration.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct MemoryConfig {
/// The emulated EVM linear heap memory size in bytes.
pub heap_size: u32,
/// The PVM stack size in bytes.
pub stack_size: u32,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
heap_size: 64 * 1024,
stack_size: 32 * 1024,
}
}
}
@@ -9,6 +9,15 @@ pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// The calldata size global variable name. /// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize"; pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The heap size global variable name.
pub static GLOBAL_HEAP_SIZE: &str = "__heap_size";
/// The heap memory global variable name.
pub static GLOBAL_HEAP_MEMORY: &str = "__heap_memory";
/// The spill buffer global variable name.
pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer";
/// The deployer call header size that consists of: /// The deployer call header size that consists of:
/// - bytecode hash (32 bytes) /// - bytecode hash (32 bytes)
pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD; pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
@@ -4,61 +4,98 @@
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Argument<'ctx> { pub struct Argument<'ctx> {
/// The actual LLVM operand. /// The actual LLVM operand.
pub value: inkwell::values::BasicValueEnum<'ctx>, pub value: Value<'ctx>,
/// The original AST value. Used mostly for string literals. /// The original AST value. Used mostly for string literals.
pub original: Option<String>, pub original: Option<String>,
/// The preserved constant value, if available. /// The preserved constant value, if available.
pub constant: Option<num::BigUint>, pub constant: Option<num::BigUint>,
} }
/// The function argument can be either a pointer or a integer value.
/// This disambiguation allows for lazy loading of variables.
#[derive(Clone, Debug)]
pub enum Value<'ctx> {
Register(inkwell::values::BasicValueEnum<'ctx>),
Pointer {
pointer: crate::polkavm::context::Pointer<'ctx>,
id: String,
},
}
impl<'ctx> Argument<'ctx> { impl<'ctx> Argument<'ctx> {
/// The calldata offset argument index. /// A shortcut constructor for register arguments.
pub const ARGUMENT_INDEX_CALLDATA_OFFSET: usize = 0; pub fn value(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
/// The calldata length argument index.
pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1;
/// A shortcut constructor.
pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self { Self {
value, value: Value::Register(value),
original: None, original: None,
constant: None, constant: None,
} }
} }
/// A shortcut constructor. /// A shortcut constructor for stack arguments.
pub fn new_with_original( pub fn pointer(pointer: crate::polkavm::context::Pointer<'ctx>, id: String) -> Self {
value: inkwell::values::BasicValueEnum<'ctx>,
original: String,
) -> Self {
Self { Self {
value, value: Value::Pointer { pointer, id },
original: Some(original), original: None,
constant: None, constant: None,
} }
} }
/// A shortcut constructor. /// Set the original decleratation value.
pub fn new_with_constant( pub fn with_original(mut self, original: String) -> Self {
value: inkwell::values::BasicValueEnum<'ctx>, self.original = Some(original);
constant: num::BigUint, self
) -> Self { }
Self {
value, /// Set the constant value.
original: None, pub fn with_constant(mut self, constant: num::BigUint) -> Self {
constant: Some(constant), self.constant = Some(constant);
} self
} }
/// Returns the inner LLVM value. /// Returns the inner LLVM value.
pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> { ///
self.value /// Panics if `self` is a pointer argument.
pub fn _to_llvm_value(&self) -> inkwell::values::BasicValueEnum<'ctx> {
match &self.value {
Value::Register(value) => *value,
Value::Pointer { .. } => unreachable!("invalid register value access"),
}
}
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn access<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
match &self.value {
Value::Register(value) => Ok(*value),
Value::Pointer { pointer, id } => context.build_load(*pointer, id),
}
}
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn as_pointer<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<crate::polkavm::context::Pointer<'ctx>> {
match &self.value {
Value::Register(value) => {
let pointer = context.build_alloca_at_entry(context.word_type(), "pvm_arg");
context.build_store(pointer, *value)?;
Ok(pointer)
}
Value::Pointer { pointer, .. } => Ok(*pointer),
}
} }
} }
impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> { impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> {
fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self { fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self::new(value) Self::value(value)
} }
} }
@@ -31,6 +31,31 @@ impl Entry {
context.xlen_type().get_undef(), context.xlen_type().get_undef(),
); );
context.set_global(
crate::polkavm::GLOBAL_HEAP_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.xlen_type().const_zero(),
);
let heap_memory_type = context
.byte_type()
.array_type(context.memory_config.heap_size);
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY,
heap_memory_type,
AddressSpace::Stack,
heap_memory_type.const_zero(),
);
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
context.set_global(
crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER,
address_type,
AddressSpace::Stack,
address_type.const_zero(),
);
Ok(()) Ok(())
} }
@@ -5,6 +5,7 @@ pub mod deploy_code;
pub mod entry; pub mod entry;
pub mod revive; pub mod revive;
pub mod runtime_code; pub mod runtime_code;
pub mod sbrk;
/// The main entry function name. /// The main entry function name.
pub const FUNCTION_ENTRY: &str = "__entry"; pub const FUNCTION_ENTRY: &str = "__entry";
@@ -0,0 +1,144 @@
//! Emulates the linear EVM heap memory via a simulated `sbrk` system call.
use inkwell::values::BasicValue;
use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Simulates the `sbrk` system call, reproducing the semantics of the EVM heap memory.
///
/// Parameters:
/// - The `offset` into the emulated EVM heap memory.
/// - The `size` of the allocation emulated EVM heap memory.
///
/// Returns:
/// - A pointer to the EVM heap memory at given `offset`.
///
/// Semantics:
/// - Traps if the offset is out of bounds.
/// - Aligns the total heap memory size to the EVM word size.
/// - Traps if the memory size would be greater than the configured EVM heap memory size.
/// - Maintains the total memory size (`msize`) in global heap size value.
pub struct Sbrk;
impl<D> RuntimeFunction<D> for Sbrk
where
D: Dependency + Clone,
{
const NAME: &'static str = "__sbrk_internal";
const ATTRIBUTES: &'static [Attribute] = &[
Attribute::NoFree,
Attribute::NoRecurse,
Attribute::WillReturn,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.llvm().ptr_type(Default::default()).fn_type(
&[context.xlen_type().into(), context.xlen_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let size = Self::paramater(context, 1).into_int_value();
let trap_block = context.append_basic_block("trap");
let offset_in_bounds_block = context.append_basic_block("offset_in_bounds");
let is_offset_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGE,
offset,
context.heap_size(),
"offset_out_of_bounds",
)?;
context.build_conditional_branch(
is_offset_out_of_bounds,
trap_block,
offset_in_bounds_block,
)?;
context.set_basic_block(trap_block);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
context.set_basic_block(offset_in_bounds_block);
let mask = context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64 - 1, false);
let total_size = context
.builder()
.build_int_add(offset, size, "total_size")?;
let memory_size = context.builder().build_and(
context.builder().build_int_add(total_size, mask, "mask")?,
context.builder().build_not(mask, "mask_not")?,
"memory_size",
)?;
let size_in_bounds_block = context.append_basic_block("size_in_bounds");
let is_size_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
memory_size,
context.heap_size(),
"size_out_of_bounds",
)?;
context.build_conditional_branch(
is_size_out_of_bounds,
trap_block,
size_in_bounds_block,
)?;
context.set_basic_block(size_in_bounds_block);
let return_block = context.append_basic_block("return_pointer");
let new_size_block = context.append_basic_block("new_size");
let is_new_size = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
memory_size,
context
.get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.into_int_value(),
"is_new_size",
)?;
context.build_conditional_branch(is_new_size, new_size_block, return_block)?;
context.set_basic_block(new_size_block);
context.build_store(
context.get_global(crate::polkavm::GLOBAL_HEAP_SIZE)?.into(),
memory_size,
)?;
context.build_unconditional_branch(return_block);
context.set_basic_block(return_block);
Ok(Some(
context
.build_gep(
context
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY)?
.into(),
&[context.xlen_type().const_zero(), offset],
context.byte_type(),
"allocation_start_pointer",
)
.value
.as_basic_value_enum(),
))
}
}
impl<D> WriteLLVM<D> for Sbrk
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
+44 -24
View File
@@ -26,6 +26,7 @@ use inkwell::debug_info::DIScope;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer; use crate::optimizer::Optimizer;
use crate::polkavm::DebugConfig; use crate::polkavm::DebugConfig;
@@ -33,6 +34,7 @@ use crate::polkavm::Dependency;
use crate::target_machine::target::Target; use crate::target_machine::target::Target;
use crate::target_machine::TargetMachine; use crate::target_machine::TargetMachine;
use crate::PolkaVMLoadHeapWordFunction; use crate::PolkaVMLoadHeapWordFunction;
use crate::PolkaVMSbrkFunction;
use crate::PolkaVMStoreHeapWordFunction; use crate::PolkaVMStoreHeapWordFunction;
use self::address_space::AddressSpace; use self::address_space::AddressSpace;
@@ -85,6 +87,8 @@ where
loop_stack: Vec<Loop<'ctx>>, loop_stack: Vec<Loop<'ctx>>,
/// The extra LLVM arguments that were used during target initialization. /// The extra LLVM arguments that were used during target initialization.
llvm_arguments: &'ctx [String], llvm_arguments: &'ctx [String],
/// The PVM memory configuration.
memory_config: MemoryConfig,
/// The project dependency manager. It can be any entity implementing the trait. /// 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 /// The manager is used to get information about contracts and their dependencies during
@@ -116,9 +120,6 @@ where
/// The loop stack default capacity. /// The loop stack default capacity.
const LOOP_STACK_INITIAL_CAPACITY: usize = 16; const LOOP_STACK_INITIAL_CAPACITY: usize = 16;
/// The PolkaVM minimum stack size.
const POLKAVM_STACK_SIZE: u32 = 0x4000;
/// Link in the stdlib module. /// Link in the stdlib module.
fn link_stdlib_module( fn link_stdlib_module(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
@@ -218,6 +219,7 @@ where
} }
/// Initializes a new LLVM context. /// Initializes a new LLVM context.
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
module: inkwell::module::Module<'ctx>, module: inkwell::module::Module<'ctx>,
@@ -226,11 +228,12 @@ where
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: DebugConfig, debug_config: DebugConfig,
llvm_arguments: &'ctx [String], llvm_arguments: &'ctx [String],
memory_config: MemoryConfig,
) -> Self { ) -> Self {
Self::set_data_layout(llvm, &module); Self::set_data_layout(llvm, &module);
Self::link_stdlib_module(llvm, &module); Self::link_stdlib_module(llvm, &module);
Self::link_polkavm_imports(llvm, &module); Self::link_polkavm_imports(llvm, &module);
Self::set_polkavm_stack_size(llvm, &module, Self::POLKAVM_STACK_SIZE); Self::set_polkavm_stack_size(llvm, &module, memory_config.stack_size);
Self::set_module_flags(llvm, &module); Self::set_module_flags(llvm, &module);
let intrinsics = Intrinsics::new(llvm, &module); let intrinsics = Intrinsics::new(llvm, &module);
@@ -254,6 +257,7 @@ where
current_function: None, current_function: None,
loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY),
llvm_arguments, llvm_arguments,
memory_config,
dependency_manager, dependency_manager,
include_metadata_hash, include_metadata_hash,
@@ -644,6 +648,7 @@ where
self.include_metadata_hash, self.include_metadata_hash,
self.debug_config.clone(), self.debug_config.clone(),
self.llvm_arguments, self.llvm_arguments,
self.memory_config,
) )
}) })
} }
@@ -750,7 +755,9 @@ where
address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<Pointer<'ctx>> { ) -> anyhow::Result<Pointer<'ctx>> {
let address_type = self.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS); let address_type = self.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_pointer = self.build_alloca_at_entry(address_type, "address_pointer"); let address_pointer = self
.get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
.into();
let address_truncated = let address_truncated =
self.builder() self.builder()
.build_int_truncate(address, address_type, "address_truncated")?; .build_int_truncate(address, address_type, "address_truncated")?;
@@ -1082,32 +1089,40 @@ where
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>, size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> { ) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
Ok(self let call_site_value = self.builder().build_call(
.builder() <PolkaVMSbrkFunction as RuntimeFunction<D>>::declaration(self).function_value(),
.build_call( &[offset.into(), size.into()],
self.runtime_api_method(revive_runtime_api::polkavm_imports::SBRK), "alloc_start",
&[offset.into(), size.into()], )?;
"call_sbrk",
)? call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NonNull as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NoUndef as u32, 0),
);
Ok(call_site_value
.try_as_basic_value() .try_as_basic_value()
.left() .left()
.expect("sbrk returns a pointer") .unwrap_or_else(|| {
panic!(
"revive runtime function {} should return a value",
<PolkaVMSbrkFunction as RuntimeFunction<D>>::NAME,
)
})
.into_pointer_value()) .into_pointer_value())
} }
/// Build a call to PolkaVM `msize` for querying the linear memory size. /// Build a call to PolkaVM `msize` for querying the linear memory size.
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> { pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let memory_size_pointer = self Ok(self
.module() .get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE) .into_int_value())
.expect("the memory size symbol should have been declared")
.as_pointer_value();
let memory_size_value = self.builder().build_load(
self.xlen_type(),
memory_size_pointer,
"memory_size_value",
)?;
Ok(memory_size_value.into_int_value())
} }
/// Returns a pointer to `offset` into the heap, allocating /// Returns a pointer to `offset` into the heap, allocating
@@ -1426,4 +1441,9 @@ where
pub fn optimizer_settings(&self) -> &OptimizerSettings { pub fn optimizer_settings(&self) -> &OptimizerSettings {
self.optimizer.settings() self.optimizer.settings()
} }
pub fn heap_size(&self) -> inkwell::values::IntValue<'ctx> {
self.xlen_type()
.const_int(self.memory_config.heap_size as u64, false)
}
} }
@@ -19,7 +19,7 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context context
.word_type() .word_type()
.fn_type(&[context.word_type().into()], false) .fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
} }
fn emit_body<'ctx>( fn emit_body<'ctx>(
@@ -59,7 +59,7 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context context
.word_type() .word_type()
.fn_type(&[context.word_type().into()], false) .fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
} }
fn emit_body<'ctx>( fn emit_body<'ctx>(
@@ -94,7 +94,10 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type( context.void_type().fn_type(
&[context.word_type().into(), context.word_type().into()], &[
context.llvm().ptr_type(Default::default()).into(),
context.llvm().ptr_type(Default::default()).into(),
],
false, false,
) )
} }
@@ -138,7 +141,10 @@ where
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type( context.void_type().fn_type(
&[context.word_type().into(), context.word_type().into()], &[
context.llvm().ptr_type(Default::default()).into(),
context.llvm().ptr_type(Default::default()).into(),
],
false, false,
) )
} }
@@ -173,9 +179,17 @@ where
fn emit_load<'ctx, D: Dependency + Clone>( fn emit_load<'ctx, D: Dependency + Clone>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
mut key: BasicValueEnum<'ctx>, key: BasicValueEnum<'ctx>,
transient: bool, transient: bool,
) -> anyhow::Result<BasicValueEnum<'ctx>> { ) -> anyhow::Result<BasicValueEnum<'ctx>> {
let mut key = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
key.into_pointer_value(),
),
"key",
)?;
if !transient { if !transient {
key = context.build_byte_swap(key)?; key = context.build_byte_swap(key)?;
} }
@@ -217,10 +231,26 @@ fn emit_load<'ctx, D: Dependency + Clone>(
fn emit_store<'ctx, D: Dependency + Clone>( fn emit_store<'ctx, D: Dependency + Clone>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
mut key: BasicValueEnum<'ctx>, key: BasicValueEnum<'ctx>,
mut value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
transient: bool, transient: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut key = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
key.into_pointer_value(),
),
"key",
)?;
let mut value = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
value.into_pointer_value(),
),
"key",
)?;
if !transient { if !transient {
key = context.build_byte_swap(key)?; key = context.build_byte_swap(key)?;
value = context.build_byte_swap(value)?; value = context.build_byte_swap(value)?;
@@ -23,6 +23,7 @@ pub fn create_context(
true, true,
Default::default(), Default::default(),
Default::default(), Default::default(),
Default::default(),
) )
} }
+28 -14
View File
@@ -63,7 +63,6 @@ where
let non_overflow_block = context.append_basic_block("shift_left_non_overflow"); let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
let join_block = context.append_basic_block("shift_left_join"); let join_block = context.append_basic_block("shift_left_join");
let result_pointer = context.build_alloca(context.word_type(), "shift_left_result_pointer");
let condition_is_overflow = context.builder().build_int_compare( let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT, inkwell::IntPredicate::UGT,
shift, shift,
@@ -73,7 +72,6 @@ where
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block); context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.word_const(0))?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block); context.set_basic_block(non_overflow_block);
@@ -81,11 +79,17 @@ where
context context
.builder() .builder()
.build_left_shift(value, shift, "shift_left_non_overflow_result")?; .build_left_shift(value, shift, "shift_left_non_overflow_result")?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(join_block); context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_left_result") let result = context
.builder()
.build_phi(context.word_type(), "shift_left_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the bitwise shift right. /// Translates the bitwise shift right.
@@ -101,7 +105,6 @@ where
let non_overflow_block = context.append_basic_block("shift_right_non_overflow"); let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
let join_block = context.append_basic_block("shift_right_join"); let join_block = context.append_basic_block("shift_right_join");
let result_pointer = context.build_alloca(context.word_type(), "shift_right_result_pointer");
let condition_is_overflow = context.builder().build_int_compare( let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT, inkwell::IntPredicate::UGT,
shift, shift,
@@ -111,7 +114,6 @@ where
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block); context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.word_const(0))?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block); context.set_basic_block(non_overflow_block);
@@ -121,11 +123,17 @@ where
false, false,
"shift_right_non_overflow_result", "shift_right_non_overflow_result",
)?; )?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(join_block); context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_result") let result = context
.builder()
.build_phi(context.word_type(), "shift_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the arithmetic bitwise shift right. /// Translates the arithmetic bitwise shift right.
@@ -145,8 +153,6 @@ where
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow"); let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
let join_block = context.append_basic_block("shift_right_arithmetic_join"); let join_block = context.append_basic_block("shift_right_arithmetic_join");
let result_pointer =
context.build_alloca(context.word_type(), "shift_right_arithmetic_result_pointer");
let condition_is_overflow = context.builder().build_int_compare( let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT, inkwell::IntPredicate::UGT,
shift, shift,
@@ -174,11 +180,9 @@ where
)?; )?;
context.set_basic_block(overflow_positive_block); context.set_basic_block(overflow_positive_block);
context.build_store(result_pointer, context.word_const(0))?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(overflow_negative_block); context.set_basic_block(overflow_negative_block);
context.build_store(result_pointer, context.word_type().const_all_ones())?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block); context.set_basic_block(non_overflow_block);
@@ -188,11 +192,21 @@ where
true, true,
"shift_right_arithmetic_non_overflow_result", "shift_right_arithmetic_non_overflow_result",
)?; )?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(join_block); context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_arithmetic_result") let result = context
.builder()
.build_phi(context.word_type(), "shift_arithmetic_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(
&context.word_type().const_all_ones(),
overflow_negative_block,
),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the `byte` instruction, extracting the byte of `operand_2` /// Translates the `byte` instruction, extracting the byte of `operand_2`
+16 -16
View File
@@ -2,6 +2,7 @@
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
@@ -49,7 +50,9 @@ where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS); let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_pointer = context.build_alloca_at_entry(address_type, "origin_address"); let address_pointer: Pointer<'_> = context
.get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
.into();
context.build_store(address_pointer, address_type.const_zero())?; context.build_store(address_pointer, address_type.const_zero())?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::ORIGIN, revive_runtime_api::polkavm_imports::ORIGIN,
@@ -97,13 +100,13 @@ where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let output_pointer = context.build_alloca_at_entry(context.word_type(), "blockhash_out_ptr"); let output_pointer = context.build_alloca_at_entry(context.word_type(), "blockhash_out_ptr");
let index_ptr = context.build_alloca_at_entry(context.word_type(), "blockhash_index_ptr"); let index_pointer = context.build_alloca_at_entry(context.word_type(), "blockhash_index_ptr");
context.build_store(index_ptr, index)?; context.build_store(index_pointer, index)?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_HASH, revive_runtime_api::polkavm_imports::BLOCK_HASH,
&[ &[
index_ptr.to_int(context).into(), index_pointer.to_int(context).into(),
output_pointer.to_int(context).into(), output_pointer.to_int(context).into(),
], ],
); );
@@ -127,10 +130,9 @@ pub fn coinbase<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let pointer = context.build_alloca_at_entry( let pointer: Pointer<'_> = context
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS), .get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
"coinbase_output", .into();
);
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_AUTHOR, revive_runtime_api::polkavm_imports::BLOCK_AUTHOR,
&[pointer.to_int(context).into()], &[pointer.to_int(context).into()],
@@ -155,10 +157,9 @@ pub fn address<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let pointer = context.build_alloca_at_entry( let pointer: Pointer<'_> = context
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS), .get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
"address_output", .into();
);
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::ADDRESS, revive_runtime_api::polkavm_imports::ADDRESS,
&[pointer.to_int(context).into()], &[pointer.to_int(context).into()],
@@ -173,10 +174,9 @@ pub fn caller<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let pointer = context.build_alloca_at_entry( let pointer: Pointer<'_> = context
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS), .get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
"address_output", .into();
);
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::CALLER, revive_runtime_api::polkavm_imports::CALLER,
&[pointer.to_int(context).into()], &[pointer.to_int(context).into()],
+6 -10
View File
@@ -119,10 +119,8 @@ where
_ => error, _ => error,
})?; })?;
if contract_path.as_str() == parent { if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant( return Ok(Argument::value(context.word_const(0).as_basic_value_enum())
context.word_const(0).as_basic_value_enum(), .with_constant(num::BigUint::zero()));
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime { } else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier); anyhow::bail!("type({}).runtimeCode is not supported", identifier);
} }
@@ -131,7 +129,7 @@ where
let hash_value = context let hash_value = context
.word_const_str_hex(hash_string.as_str()) .word_const_str_hex(hash_string.as_str())
.as_basic_value_enum(); .as_basic_value_enum();
Ok(Argument::new_with_original(hash_value, hash_string)) Ok(Argument::value(hash_value).with_original(hash_string))
} }
/// Translates the deploy call header size instruction. the header consists of /// Translates the deploy call header size instruction. the header consists of
@@ -160,10 +158,8 @@ where
_ => error, _ => error,
})?; })?;
if contract_path.as_str() == parent { if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant( return Ok(Argument::value(context.word_const(0).as_basic_value_enum())
context.word_const(0).as_basic_value_enum(), .with_constant(num::BigUint::zero()));
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime { } else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier); anyhow::bail!("type({}).runtimeCode is not supported", identifier);
} }
@@ -172,5 +168,5 @@ where
let size_value = context let size_value = context
.word_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64) .word_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64)
.as_basic_value_enum(); .as_basic_value_enum();
Ok(Argument::new_with_constant(size_value, size_bigint)) Ok(Argument::value(size_value).with_constant(size_bigint))
} }
@@ -28,7 +28,7 @@ pub fn value<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let output_pointer = context.build_alloca(context.value_type(), "value_transferred"); let output_pointer = context.build_alloca_at_entry(context.value_type(), "value_transferred");
context.build_store(output_pointer, context.word_const(0))?; context.build_store(output_pointer, context.word_const(0))?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::VALUE_TRANSFERRED, revive_runtime_api::polkavm_imports::VALUE_TRANSFERRED,
@@ -46,8 +46,7 @@ where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let address_pointer = context.build_address_argument_store(address)?; let address_pointer = context.build_address_argument_store(address)?;
let balance_pointer = context.build_alloca_at_entry(context.word_type(), "balance_pointer");
let balance_pointer = context.build_alloca(context.word_type(), "balance_pointer");
let balance = context.builder().build_ptr_to_int( let balance = context.builder().build_ptr_to_int(
balance_pointer.value, balance_pointer.value,
context.xlen_type(), context.xlen_type(),
@@ -69,7 +68,7 @@ pub fn self_balance<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let balance_pointer = context.build_alloca(context.word_type(), "balance_pointer"); let balance_pointer = context.build_alloca_at_entry(context.word_type(), "balance_pointer");
let balance = context.builder().build_ptr_to_int( let balance = context.builder().build_ptr_to_int(
balance_pointer.value, balance_pointer.value,
context.xlen_type(), context.xlen_type(),
+17 -10
View File
@@ -3,6 +3,7 @@
use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::PolkaVMArgument;
use crate::PolkaVMLoadStorageWordFunction; use crate::PolkaVMLoadStorageWordFunction;
use crate::PolkaVMLoadTransientStorageWordFunction; use crate::PolkaVMLoadTransientStorageWordFunction;
use crate::PolkaVMStoreStorageWordFunction; use crate::PolkaVMStoreStorageWordFunction;
@@ -11,14 +12,14 @@ use crate::PolkaVMStoreTransientStorageWordFunction;
/// Translates the storage load. /// Translates the storage load.
pub fn load<'ctx, D>( pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME; let name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context); let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.into()]; let arguments = [position.as_pointer(context)?.value.into()];
Ok(context Ok(context
.build_call(declaration, &arguments, "storage_load") .build_call(declaration, &arguments, "storage_load")
.unwrap_or_else(|| panic!("runtime function {name} should return a value"))) .unwrap_or_else(|| panic!("runtime function {name} should return a value")))
@@ -27,14 +28,17 @@ where
/// Translates the storage store. /// Translates the storage store.
pub fn store<'ctx, D>( pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
value: inkwell::values::IntValue<'ctx>, value: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let declaration = <PolkaVMStoreStorageWordFunction as RuntimeFunction<D>>::declaration(context); let declaration = <PolkaVMStoreStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.into(), value.into()]; let arguments = [
position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "storage_store"); context.build_call(declaration, &arguments, "storage_store");
Ok(()) Ok(())
} }
@@ -42,13 +46,13 @@ where
/// Translates the transient storage load. /// Translates the transient storage load.
pub fn transient_load<'ctx, D>( pub fn transient_load<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::NAME; let name = <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::NAME;
let arguments = [position.into()]; let arguments = [position.as_pointer(context)?.value.into()];
let declaration = let declaration =
<PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context); <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
@@ -59,15 +63,18 @@ where
/// Translates the transient storage store. /// Translates the transient storage store.
pub fn transient_store<'ctx, D>( pub fn transient_store<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
value: inkwell::values::IntValue<'ctx>, value: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let declaration = let declaration =
<PolkaVMStoreTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context); <PolkaVMStoreTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.into(), value.into()]; let arguments = [
position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "transient_storage_store"); context.build_call(declaration, &arguments, "transient_storage_store");
Ok(()) Ok(())
} }
+3
View File
@@ -7,6 +7,7 @@ pub mod evm;
pub use self::r#const::*; pub use self::r#const::*;
use crate::debug_config::DebugConfig; use crate::debug_config::DebugConfig;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
use anyhow::Context as AnyhowContext; use anyhow::Context as AnyhowContext;
@@ -90,6 +91,7 @@ pub trait Dependency {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: DebugConfig, debug_config: DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: MemoryConfig,
) -> anyhow::Result<String>; ) -> anyhow::Result<String>;
/// Resolves a full contract path. /// Resolves a full contract path.
@@ -111,6 +113,7 @@ impl Dependency for DummyDependency {
_include_metadata_hash: bool, _include_metadata_hash: bool,
_debug_config: DebugConfig, _debug_config: DebugConfig,
_llvm_arguments: &[String], _llvm_arguments: &[String],
_memory_config: MemoryConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
Ok(String::new()) Ok(String::new())
} }
+9 -2
View File
@@ -10,12 +10,19 @@ description = "Execute revive contracts in a simulated blockchain runtime"
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["codec", "scale-info"] ignored = ["codec", "scale-info"]
[[bin]]
name = "revive-runner"
path = "src/main.rs"
[features] [features]
std = ["polkadot-sdk/std"] std = ["polkadot-sdk/std"]
default = ["solidity"] default = ["solidity"]
solidity = ["revive-solidity", "revive-differential"] solidity = ["revive-solidity", "revive-differential", "revive-llvm-context"]
[dependencies] [dependencies]
env_logger = { workspace = true }
clap = { workspace = true, features = ["help", "std", "derive"] }
anyhow = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
hex = { workspace = true, features = ["serde"] } hex = { workspace = true, features = ["serde"] }
@@ -34,4 +41,4 @@ polkadot-sdk.features = [
revive-solidity = { workspace = true, optional = true } revive-solidity = { workspace = true, optional = true }
revive-differential = { workspace = true, optional = true } revive-differential = { workspace = true, optional = true }
revive-llvm-context = { workspace = true } revive-llvm-context = { workspace = true, optional = true }
+27
View File
@@ -0,0 +1,27 @@
# revive-runner
The revive runner is a helper utility aiding in contract debugging.
Given a PVM contract blob, it will upload, deploy and call that contract using a local, stand-alone un-blockchained pallet revive (which is our execution layer).
This is somewhat similar to the geth `evm` utility binary.
## Installation
The `revive-runner` does not depend on the compiler itself, hence installing this utility does not depend on LLVM, so no LLVM build is required.
Inside the root `revive` repository directory, execute:
```bash
make install-revive-runner
```
Which will install the `revive-runner` using `cargo`.
## Usage
Set the `RUST_LOG` environment varibale to the `trace` level to see the full PolkaVM execution trace. For example:
```bash
RUST_LOG=trace revive-runner -f mycontract.pvm -c a9059cbb000000000000000000000000f24ff3a9cf04c71dbc94d0b566f7a27b94566cac0000000000000000000000000000000000000000000000000000000000000000
```
+93
View File
@@ -0,0 +1,93 @@
use std::path::PathBuf;
use clap::Parser;
use revive_runner::{Code, OptionalHex, Specs, SpecsAction::*, TestAddress};
/// Execute revive PolkaVM contracts locally.
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Arguments {
/// The hex encoded calldata for the contract call.
#[arg(short, long)]
calldata: Option<String>,
/// The hex encoded calldata for the contract deployment.
#[arg(short, long)]
deploy_calldata: Option<String>,
/// The hex encoded contract code blob to instantiate and execute.
#[arg(short, long)]
blob: Option<String>,
/// The contract code to instantiate and execute.
#[arg(short, long)]
file: Option<PathBuf>,
/// The origin account used to initiate the deploy and call transactions.
#[arg(short, long)]
origin: Option<TestAddress>,
/// The value the call transaction is endowed with.
#[arg(short, long)]
value: Option<u128>,
/// The value the deploy transaction is endowed with.
#[arg(long)]
deploy_value: Option<u128>,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let arguments = Arguments::parse();
let code = match (arguments.blob, arguments.file) {
(Some(blob), None) => hex::decode(blob)
.map_err(|error| anyhow::anyhow!("expected hex encoded PVM blob: {error}"))?,
(None, Some(file)) => std::fs::read(&file).map_err(|error| {
anyhow::anyhow!("unable to read PVM file {}: {error}", file.display())
})?,
_ => anyhow::bail!("should either provide a PVM blob or a PVM file"),
};
let calldata = match arguments.calldata {
Some(calldata) => hex::decode(calldata)
.map_err(|error| anyhow::anyhow!("expected hex encoded calldata: {error}"))?,
None => vec![],
};
let deploy_calldata = match arguments.deploy_calldata {
Some(calldata) => hex::decode(calldata)
.map_err(|error| anyhow::anyhow!("expected hex encoded calldata: {error}"))?,
None => vec![],
};
let origin = arguments.origin.unwrap_or(TestAddress::Alice);
let actions = vec![
Instantiate {
origin: origin.clone(),
value: arguments.deploy_value.unwrap_or(0),
gas_limit: None,
storage_deposit_limit: None,
code: Code::Bytes(code),
data: deploy_calldata,
salt: OptionalHex::default(),
},
Call {
origin,
dest: TestAddress::Instantiated(0),
value: arguments.value.unwrap_or(0),
gas_limit: None,
storage_deposit_limit: None,
data: calldata,
},
];
Specs {
actions,
differential: false,
..Default::default()
}
.run();
Ok(())
}
+38 -2
View File
@@ -1,9 +1,11 @@
use std::time::Instant; use std::{str::FromStr, time::Instant};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::*; use crate::*;
use alloy_primitives::{keccak256, Address}; use alloy_primitives::keccak256;
#[cfg(feature = "revive-solidity")]
use alloy_primitives::Address;
#[cfg(feature = "revive-solidity")] #[cfg(feature = "revive-solidity")]
use revive_differential::{Evm, EvmLog}; use revive_differential::{Evm, EvmLog};
#[cfg(feature = "revive-solidity")] #[cfg(feature = "revive-solidity")]
@@ -156,6 +158,39 @@ impl TestAddress {
} }
} }
impl FromStr for TestAddress {
type Err = &'static str;
fn from_str(value: &str) -> Result<Self, Self::Err> {
value.try_into()
}
}
impl TryFrom<&str> for TestAddress {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"alice" => Ok(Self::Alice),
"bob" => Ok(Self::Bob),
"charlie" => Ok(Self::Charlie),
value => {
if let Ok(value) = value.parse() {
return Ok(Self::Instantiated(value));
}
if let Ok(value) = hex::decode(value) {
if value.len() == 20 {
return Ok(Self::AccountId(H160(value.try_into().unwrap())));
}
}
Err("can not parse into test address")
}
}
}
}
/// Specs for a contract test /// Specs for a contract test
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
@@ -212,6 +247,7 @@ impl Specs {
/// Helper to allow not specifying the code bytes or path directly in the runner.json /// Helper to allow not specifying the code bytes or path directly in the runner.json
/// - Replace `Code::Bytes(bytes)` if `bytes` are empty: read `contract_file` /// - Replace `Code::Bytes(bytes)` if `bytes` are empty: read `contract_file`
/// - Replace `Code::Solidity{ path, ..}` if `path` is not provided: replace `path` with `contract_file` /// - Replace `Code::Solidity{ path, ..}` if `path` is not provided: replace `path` with `contract_file`
#[allow(unused_variables)]
pub fn replace_empty_code(&mut self, contract_name: &str, contract_path: &str) { pub fn replace_empty_code(&mut self, contract_name: &str, contract_path: &str) {
for action in self.actions.iter_mut() { for action in self.actions.iter_mut() {
let code = match action { let code = match action {
-28
View File
@@ -5,34 +5,6 @@
// Missing builtins // Missing builtins
#define EVM_WORD_SIZE 32
#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1))
#define MAX_MEMORY_SIZE (64 * 1024)
char __memory[MAX_MEMORY_SIZE];
uint32_t __memory_size = 0;
void * __sbrk_internal(uint32_t offset, uint32_t size) {
if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) {
POLKAVM_TRAP();
}
uint32_t new_size = ALIGN(offset + size);
if (new_size > MAX_MEMORY_SIZE) {
POLKAVM_TRAP();
}
if (new_size > __memory_size) {
__memory_size = new_size;
}
return (void *)&__memory[offset];
}
void * memset(void *b, int c, size_t len) {
uint8_t *dest = b;
while (len-- > 0) *dest++ = c;
return b;
}
void * memcpy(void *dst, const void *_src, size_t len) { void * memcpy(void *dst, const void *_src, size_t len) {
uint8_t *dest = dst; uint8_t *dest = dst;
const uint8_t *src = _src; const uint8_t *src = _src;
+1 -10
View File
@@ -2,14 +2,6 @@ use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, sup
include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs")); include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs"));
/// The emulated EVM heap memory global symbol.
pub static MEMORY: &str = "__memory";
/// The emulated EVM heap memory size global symbol.
pub static MEMORY_SIZE: &str = "__memory_size";
pub static SBRK: &str = "__sbrk_internal";
pub static ADDRESS: &str = "address"; pub static ADDRESS: &str = "address";
pub static BALANCE: &str = "balance"; pub static BALANCE: &str = "balance";
@@ -78,8 +70,7 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// All imported runtime API symbols. /// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage. /// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [ pub static IMPORTS: [&str; 33] = [
SBRK,
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
+11
View File
@@ -55,6 +55,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let path = match input_files.len() { let path = match input_files.len() {
1 => input_files.first().expect("Always exists"), 1 => input_files.first().expect("Always exists"),
@@ -80,6 +81,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
Ok(build) Ok(build)
@@ -92,6 +94,7 @@ pub fn llvm_ir(
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let path = match input_files.len() { let path = match input_files.len() {
1 => input_files.first().expect("Always exists"), 1 => input_files.first().expect("Always exists"),
@@ -109,6 +112,7 @@ pub fn llvm_ir(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
Ok(build) Ok(build)
@@ -131,6 +135,7 @@ pub fn standard_output<T: Compiler>(
suppressed_warnings: Option<Vec<ResolcWarning>>, suppressed_warnings: Option<Vec<ResolcWarning>>,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -189,12 +194,14 @@ pub fn standard_output<T: Compiler>(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
Ok(build) Ok(build)
} }
/// Runs the standard JSON mode. /// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json<T: Compiler>( pub fn standard_json<T: Compiler>(
solc: &mut T, solc: &mut T,
detect_missing_libraries: bool, detect_missing_libraries: bool,
@@ -203,6 +210,7 @@ pub fn standard_json<T: Compiler>(
allow_paths: Option<String>, allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -250,6 +258,7 @@ pub fn standard_json<T: Compiler>(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
build.write_to_standard_json(&mut solc_output, &solc_version)?; build.write_to_standard_json(&mut solc_output, &solc_version)?;
} }
@@ -277,6 +286,7 @@ pub fn combined_json<T: Compiler>(
output_directory: Option<PathBuf>, output_directory: Option<PathBuf>,
overwrite: bool, overwrite: bool,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let build = standard_output( let build = standard_output(
input_files, input_files,
@@ -293,6 +303,7 @@ pub fn combined_json<T: Compiler>(
suppressed_warnings, suppressed_warnings,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
let mut combined_json = solc.combined_json(input_files, format.as_str())?; let mut combined_json = solc.combined_json(input_files, format.as_str())?;
+4
View File
@@ -22,6 +22,8 @@ pub struct Input {
pub debug_config: revive_llvm_context::DebugConfig, pub debug_config: revive_llvm_context::DebugConfig,
/// The extra LLVM arguments give used for manual control. /// The extra LLVM arguments give used for manual control.
pub llvm_arguments: Vec<String>, pub llvm_arguments: Vec<String>,
/// The PVM memory configuration.
pub memory_config: revive_llvm_context::MemoryConfig,
} }
impl Input { impl Input {
@@ -33,6 +35,7 @@ impl Input {
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: Vec<String>, llvm_arguments: Vec<String>,
memory_config: revive_llvm_context::MemoryConfig,
) -> Self { ) -> Self {
Self { Self {
contract, contract,
@@ -41,6 +44,7 @@ impl Input {
optimizer_settings, optimizer_settings,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
} }
} }
} }
+1
View File
@@ -50,6 +50,7 @@ pub trait Process {
input.include_metadata_hash, input.include_metadata_hash,
input.debug_config, input.debug_config,
&input.llvm_arguments, &input.llvm_arguments,
input.memory_config,
); );
match result { match result {
@@ -78,6 +78,7 @@ impl Contract {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<ContractBuild> { ) -> anyhow::Result<ContractBuild> {
let llvm = inkwell::context::Context::create(); let llvm = inkwell::context::Context::create();
let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings); let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings);
@@ -123,6 +124,7 @@ impl Contract {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
); );
context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default()); context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default());
match self.ir { match self.ir {
+4
View File
@@ -67,6 +67,7 @@ impl Project {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let project = self.clone(); let project = self.clone();
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@@ -83,6 +84,7 @@ impl Project {
optimizer_settings.clone(), optimizer_settings.clone(),
debug_config.clone(), debug_config.clone(),
llvm_arguments.to_vec(), llvm_arguments.to_vec(),
memory_config,
); );
let process_output = { let process_output = {
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
@@ -319,6 +321,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let contract_path = project.resolve_path(identifier)?; let contract_path = project.resolve_path(identifier)?;
let contract = project let contract = project
@@ -339,6 +342,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
) )
.map_err(|error| { .map_err(|error| {
anyhow::anyhow!( anyhow::anyhow!(
+33 -1
View File
@@ -167,9 +167,41 @@ pub struct Arguments {
#[arg(long = "recursive-process-input")] #[arg(long = "recursive-process-input")]
pub recursive_process_input: Option<String>, pub recursive_process_input: Option<String>,
#[arg(long = "llvm-arg")]
/// These are passed to LLVM as the command line to allow manual control. /// These are passed to LLVM as the command line to allow manual control.
#[arg(long = "llvm-arg")]
pub llvm_arguments: Vec<String>, pub llvm_arguments: Vec<String>,
/// The emulated EVM linear heap memory static buffer size in bytes.
///
/// Unlike the EVM, due to the lack of dynamic memory metering, PVM contracts emulate
/// the EVM heap memory with a static buffer. Consequentially, instead of infinite
/// memory with exponentially growing gas costs, PVM contracts have a finite amount
/// of memory with constant gas costs available.
///
/// If the contract uses more heap memory than configured, it will compile fine but
/// eventually revert execution at runtime!
///
/// You are incentiviced to keep this value as small as possible:
/// 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,
/// The contracts total stack size in bytes.
///
/// PVM is a register machine with a traditional stack memory space for local
/// variables. This controls the total amount of stack space the contract can use.
///
/// If the contract uses more stack memory than configured, it will compile fine but
/// eventually revert execution at runtime!
///
/// You are incentiviced to keep this value as small as possible:
/// 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,
} }
impl Arguments { impl Arguments {
+10
View File
@@ -148,6 +148,11 @@ fn main_inner() -> anyhow::Result<()> {
None => true, None => true,
}; };
let memory_config = revive_llvm_context::MemoryConfig {
heap_size: arguments.heap_size,
stack_size: arguments.stack_size,
};
let build = if arguments.yul { let build = if arguments.yul {
revive_solidity::yul( revive_solidity::yul(
input_files.as_slice(), input_files.as_slice(),
@@ -156,6 +161,7 @@ fn main_inner() -> anyhow::Result<()> {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
) )
} else if arguments.llvm_ir { } else if arguments.llvm_ir {
revive_solidity::llvm_ir( revive_solidity::llvm_ir(
@@ -164,6 +170,7 @@ fn main_inner() -> anyhow::Result<()> {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
) )
} else if arguments.standard_json { } else if arguments.standard_json {
revive_solidity::standard_json( revive_solidity::standard_json(
@@ -174,6 +181,7 @@ fn main_inner() -> anyhow::Result<()> {
arguments.allow_paths, arguments.allow_paths,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
)?; )?;
return Ok(()); return Ok(());
} else if let Some(format) = arguments.combined_json { } else if let Some(format) = arguments.combined_json {
@@ -195,6 +203,7 @@ fn main_inner() -> anyhow::Result<()> {
arguments.output_directory, arguments.output_directory,
arguments.overwrite, arguments.overwrite,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
)?; )?;
return Ok(()); return Ok(());
} else { } else {
@@ -213,6 +222,7 @@ fn main_inner() -> anyhow::Result<()> {
suppressed_warnings, suppressed_warnings,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
) )
}?; }?;
+21 -5
View File
@@ -92,7 +92,7 @@ pub fn build_solidity_with_options(
SolcStandardJsonInputSettingsSelection::new_required(), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, optimizer_settings.middle_end_as_string().chars().last(),
&solc_version.default, &solc_version.default,
false, false,
), ),
@@ -102,16 +102,26 @@ pub fn build_solidity_with_options(
let mut output = solc.standard_json(input, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let debug_config = revive_llvm_context::DebugConfig::new(
None,
optimizer_settings.middle_end_as_string() != "z",
);
let project = Project::try_from_standard_json_output( let project = Project::try_from_standard_json_output(
&output, &output,
sources, sources,
libraries, libraries,
&solc_version, &solc_version,
&DEBUG_CONFIG, &debug_config,
)?; )?;
let build: crate::Build = let build: crate::Build = project.compile(
project.compile(optimizer_settings, false, DEBUG_CONFIG, Default::default())?; optimizer_settings,
false,
debug_config,
Default::default(),
Default::default(),
)?;
build.write_to_standard_json(&mut output, &solc_version)?; build.write_to_standard_json(&mut output, &solc_version)?;
Ok(output) Ok(output)
@@ -238,7 +248,13 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> {
source_code, source_code,
None, None,
)?; )?;
let _build = project.compile(optimizer_settings, false, DEBUG_CONFIG, Default::default())?; let _build = project.compile(
optimizer_settings,
false,
DEBUG_CONFIG,
Default::default(),
Default::default(),
)?;
Ok(()) Ok(())
} }
@@ -23,4 +23,4 @@
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }
File diff suppressed because one or more lines are too long
@@ -1,19 +1,33 @@
import * as path from 'path'; import * as path from "path";
const outputDir = 'artifacts'; const outputDir = "artifacts";
const binExtension = ':C.pvm'; const binExtension = ":C.pvm";
const asmExtension = ':C.pvmasm'; const asmExtension = ":C.pvmasm";
const llvmExtension = '.ll'; const llvmExtension = ".ll";
const contractSolFilename = 'contract.sol'; const contractSolFilename = "contract.sol";
const contractYulFilename = 'contract.yul'; const contractYulFilename = "contract.yul";
const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized'; const contractOptimizedLLVMFilename = contractSolFilename + ".C.optimized";
const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized'; const contractUnoptimizedLLVMFilename = contractSolFilename + ".C.unoptimized";
const pathToOutputDir = path.join(__dirname, '..', outputDir); const pathToOutputDir = path.join(__dirname, "..", outputDir);
const pathToContracts = path.join(__dirname, '..', 'src', 'contracts'); const pathToContracts = path.join(__dirname, "..", "src", "contracts");
const pathToBasicYulContract = path.join(pathToContracts, 'yul', contractYulFilename); const pathToBasicYulContract = path.join(
const pathToBasicSolContract = path.join(pathToContracts, 'solidity', contractSolFilename); pathToContracts,
const pathToSolBinOutputFile = path.join(pathToOutputDir, contractSolFilename + binExtension); "yul",
const pathToSolAsmOutputFile = path.join(pathToOutputDir, contractSolFilename + asmExtension); contractYulFilename,
);
const pathToBasicSolContract = path.join(
pathToContracts,
"solidity",
contractSolFilename,
);
const pathToSolBinOutputFile = path.join(
pathToOutputDir,
contractSolFilename + binExtension,
);
const pathToSolAsmOutputFile = path.join(
pathToOutputDir,
contractSolFilename + asmExtension,
);
export const paths = { export const paths = {
outputDir: outputDir, outputDir: outputDir,
@@ -1,44 +1,51 @@
import * as shell from 'shelljs'; import * as shell from "shelljs";
import * as fs from 'fs'; import * as fs from "fs";
import { spawnSync } from 'child_process'; import { spawnSync } from "child_process";
interface CommandResult { interface CommandResult {
output: string; output: string;
exitCode: number; exitCode: number;
} }
export const executeCommand = (command: string, stdin?: string): CommandResult => { export const executeCommand = (
if (stdin) { command: string,
const process = spawnSync(command, [], { stdin?: string,
input: stdin, ): CommandResult => {
shell: true, if (stdin) {
encoding: 'utf8', const process = spawnSync(command, [], {
maxBuffer: 30 * 1024 * 1024 input: stdin,
}); shell: true,
encoding: "utf8",
maxBuffer: 30 * 1024 * 1024,
});
return {
exitCode: process.status || 0,
output: (process.stdout || process.stderr || '').toString()
};
}
const result = shell.exec(command, { silent: true, async: false });
return { return {
exitCode: result.code, exitCode: process.status || 0,
output: result.stdout || result.stderr || '' output: (process.stdout || process.stderr || "").toString(),
}; };
}
const result = shell.exec(command, { silent: true, async: false });
return {
exitCode: result.code,
output: result.stdout || result.stderr || "",
};
}; };
export const isFolderExist = (folder: string): boolean => { 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): boolean => { export const isFileExist = (
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension); pathToFileDir: string,
fileName: string,
fileExtension: string,
): boolean => {
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension);
}; };
export const isFileEmpty = (file: string): boolean => { export const isFileEmpty = (file: string): boolean => {
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
return (fs.readFileSync(file).length === 0); return fs.readFileSync(file).length === 0;
} }
}; };
@@ -1,12 +1,11 @@
import {executeCommand} from "../src/helper"; import { executeCommand } from "../src/helper";
import { paths } from '../src/entities'; import { paths } from "../src/entities";
//id1746 //id1746
describe("Run with --asm by default", () => { describe("Run with --asm by default", () => {
const command = `resolc ${paths.pathToBasicSolContract} --asm`; const command = `resolc ${paths.pathToBasicSolContract} --asm`;
const result = executeCommand(command); const result = executeCommand(command);
const commandInvalid = 'resolc --asm'; const commandInvalid = "resolc --asm";
const resultInvalid = executeCommand(commandInvalid); const resultInvalid = executeCommand(commandInvalid);
it("Valid command exit code = 0", () => { it("Valid command exit code = 0", () => {
@@ -28,16 +27,18 @@ describe("Run with --asm by default", () => {
}); });
it("run invalid: resolc --asm", () => { it("run invalid: resolc --asm", () => {
expect(resultInvalid.output).toMatch(/(No input sources specified|Compilation aborted)/i); expect(resultInvalid.output).toMatch(
/(No input sources specified|Compilation aborted)/i,
);
}); });
it("Invalid command exit code = 1", () => { it("Invalid command exit code = 1", () => {
expect(resultInvalid.exitCode).toBe(1); expect(resultInvalid.exitCode).toBe(1);
}); });
it("Invalid solc exit code == Invalid resolc exit code", () => { it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = 'solc --asm'; const command = "solc --asm";
const solcResult = executeCommand(command); const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode); expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
}); });
}); });
@@ -1,191 +1,241 @@
import { executeCommand, isFolderExist, isFileExist, isFileEmpty } from "../src/helper"; import {
import { paths } from '../src/entities'; executeCommand,
import * as shell from 'shelljs'; isFolderExist,
import * as path from 'path'; isFileExist,
isFileEmpty,
} from "../src/helper";
import { paths } from "../src/entities";
import * as shell from "shelljs";
import * as path from "path";
//id1762 //id1762
describe("Run resolc without any options", () => { describe("Run resolc without any options", () => {
const command = 'resolc'; const command = "resolc";
const result = executeCommand(command); const result = executeCommand(command);
it("Info with help is presented", () => { it("Info with help is presented", () => {
expect(result.output).toMatch(/(Usage: resolc)/i); expect(result.output).toMatch(/(Usage: resolc)/i);
}); });
it("Exit code = 1", () => { it("Exit code = 1", () => {
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
}); });
it("solc exit code == resolc exit code", () => { it("solc exit code == resolc exit code", () => {
const command = 'solc'; const command = "solc";
const solcResult = executeCommand(command); const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode); expect(solcResult.exitCode).toBe(result.exitCode);
}); });
}); });
//#1713 //#1713
describe("Default run a command from the help", () => { describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command);
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd it("Compiler run successful", () => {
const result = executeCommand(command); expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Compiler run successful", () => { it("Exit code = 0", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); expect(result.exitCode).toBe(0);
}); });
it("Exit code = 0", () => { it("Output dir is created", () => {
expect(result.exitCode).toBe(0); expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}); });
it("Output dir is created", () => { xit("Output file is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true); // a bug on windows
}); expect(
xit("Output file is created", () => { // a bug on windows isFileExist(
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true); paths.pathToOutputDir,
}); paths.contractSolFilename,
it("the output file is not empty", () => { paths.binExtension,
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); ),
}); ).toBe(true);
it("No 'Error'/'Warning'/'Fail' in the output", () => { });
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i); it("the output file is not empty", () => {
}); expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
});
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
});
}); });
//#1818 //#1818
describe("Default run a command from the help", () => { describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command);
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd it("Compiler run successful", () => {
const result = executeCommand(command); expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Compiler run successful", () => { it("Exit code = 0", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); expect(result.exitCode).toBe(0);
}); });
it("Exit code = 0", () => { it("Output dir is created", () => {
expect(result.exitCode).toBe(0); expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}); });
it("Output dir is created", () => { xit("Output files are created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true); // a bug on windows
}); expect(
xit("Output files are created", () => { // a bug on windows isFileExist(
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true); paths.pathToOutputDir,
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true); paths.contractSolFilename,
}); paths.binExtension,
it("the output files are not empty", () => { ),
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); ).toBe(true);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false); expect(
}); isFileExist(
it("No 'Error'/'Warning'/'Fail' in the output", () => { paths.pathToOutputDir,
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i); paths.contractSolFilename,
}); paths.asmExtension,
),
).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
});
}); });
describe("Run resolc with source debug information", () => { describe("Run resolc with source debug information", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"` `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
]; // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
const command = commands[idx]; const command = commands[idx];
const result = executeCommand(command); const result = executeCommand(command);
it("Compiler run successful", () => { it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); expect(result.output).toMatch(/(Compiler run successful.)/i);
}); });
it("Exit code = 0", () => { it("Exit code = 0", () => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });
it("Output dir is created", () => { it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true); expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}); });
it("Output files are created", () => { // a bug on windows it("Output files are created", () => {
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true); // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true); expect(
}); isFileExist(
it("the output files are not empty", () => { paths.pathToOutputDir,
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); paths.contractSolFilename,
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false); paths.binExtension,
}); ),
it("No 'Error'/'Fail' in the output", () => { ).toBe(true);
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i); expect(
}); isFileExist(
} paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension,
),
).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
}
}); });
describe("Run resolc with source debug information, check LLVM debug-info", () => { describe("Run resolc with source debug information, check LLVM debug-info", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"` `resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
]; // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
const command = commands[idx]; const command = commands[idx];
const result = executeCommand(command); const result = executeCommand(command);
it("Compiler run successful", () => { it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); expect(result.output).toMatch(/(Compiler run successful.)/i);
}); });
it("Exit code = 0", () => { it("Exit code = 0", () => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });
it("Output dir is created", () => { it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true); expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
}); });
it("Output files are created", () => { // a bug on windows it("Output files are created", () => {
expect(isFileExist(paths.pathToOutputDir, paths.contractOptimizedLLVMFilename, paths.llvmExtension)).toBe(true); // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractUnoptimizedLLVMFilename, paths.llvmExtension)).toBe(true); expect(
}); isFileExist(
it("the output files are not empty", () => { paths.pathToOutputDir,
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); paths.contractOptimizedLLVMFilename,
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false); paths.llvmExtension,
}); ),
it("No 'Error'/'Fail' in the output", () => { ).toBe(true);
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i); expect(
}); isFileExist(
} paths.pathToOutputDir,
paths.contractUnoptimizedLLVMFilename,
paths.llvmExtension,
),
).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
}
}); });
describe("Standard JSON compilation with path options", () => { describe("Standard JSON compilation with path options", () => {
const contractsDir = path.join(shell.tempdir(), 'contracts-test'); const contractsDir = path.join(shell.tempdir(), "contracts-test");
const inputFile = path.join(__dirname, '..', 'src/contracts/compiled/1.json'); const inputFile = path.join(__dirname, "..", "src/contracts/compiled/1.json");
beforeAll(() => {
shell.mkdir("-p", contractsDir);
const input = JSON.parse(shell.cat(inputFile).toString());
Object.entries(input.sources).forEach(
([sourcePath, source]: [string, any]) => {
const filePath = path.join(contractsDir, sourcePath);
shell.mkdir("-p", path.dirname(filePath));
shell.ShellString(source.content).to(filePath);
},
);
});
afterAll(() => {
shell.rm("-rf", contractsDir);
});
describe("Output with all path options", () => {
let result: { exitCode: number; output: string };
beforeAll(() => { beforeAll(() => {
shell.mkdir('-p', contractsDir); const tempInputFile = path.join(contractsDir, "temp-input.json");
shell.cp(inputFile, tempInputFile);
const inputContent = shell.cat(inputFile).toString();
const input = JSON.parse(shell.cat(inputFile).toString()); const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`;
Object.entries(input.sources).forEach(([sourcePath, source]: [string, any]) => { result = executeCommand(command, inputContent);
const filePath = path.join(contractsDir, sourcePath);
shell.mkdir('-p', path.dirname(filePath)); shell.rm(tempInputFile);
shell.ShellString(source.content).to(filePath);
});
}); });
afterAll(() => { it("Compiler run successful without emiting warnings", () => {
shell.rm('-rf', contractsDir); const parsedResults = JSON.parse(result.output);
expect(
parsedResults.errors.filter(
(error: { type: string }) => error.type != "Warning",
),
).toEqual([]);
}); });
});
describe("Output with all path options", () => { });
let result: { exitCode: number; output: string };
beforeAll(() => {
const tempInputFile = path.join(contractsDir, 'temp-input.json');
shell.cp(inputFile, tempInputFile);
const inputContent = shell.cat(inputFile).toString();
const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`;
result = executeCommand(command, inputContent);
shell.rm(tempInputFile);
});
it("Compiler run successful without emiting warnings", () => {
const parsedResults = JSON.parse(result.output)
expect(parsedResults.errors.filter((error: { type: string; }) => error.type != 'Warning')).toEqual([]);
});
});
});
@@ -1,12 +1,11 @@
import {executeCommand} from "../src/helper"; import { executeCommand } from "../src/helper";
import { paths } from '../src/entities'; import { paths } from "../src/entities";
//id1743 //id1743
describe("Run with --yul by default", () => { describe("Run with --yul by default", () => {
const command = `resolc ${paths.pathToBasicYulContract} --yul`; const command = `resolc ${paths.pathToBasicYulContract} --yul`;
const result = executeCommand(command); const result = executeCommand(command);
const commandInvalid = 'resolc --yul'; const commandInvalid = "resolc --yul";
const resultInvalid = executeCommand(commandInvalid); const resultInvalid = executeCommand(commandInvalid);
it("Valid command exit code = 0", () => { it("Valid command exit code = 0", () => {
@@ -18,11 +17,11 @@ describe("Run with --yul by default", () => {
expect(result.output).toMatch(/(No output requested)/i); expect(result.output).toMatch(/(No output requested)/i);
}); });
xit("solc exit code == resolc exit code", () => {
xit("solc exit code == resolc exit code", () => { // unknown solc issue for datatype of the contract // unknown solc issue for datatype of the contract
const command = `solc ${paths.pathToBasicSolContract} --yul`; const command = `solc ${paths.pathToBasicSolContract} --yul`;
const solcResult = executeCommand(command); const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode); expect(solcResult.exitCode).toBe(result.exitCode);
}); });
it("run invalid: resolc --yul", () => { it("run invalid: resolc --yul", () => {
@@ -33,7 +32,7 @@ describe("Run with --yul by default", () => {
}); });
it("Invalid solc exit code == Invalid resolc exit code", () => { it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = 'solc --yul'; const command = "solc --yul";
const solcResult = executeCommand(command); const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode); expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
}); });
@@ -2,6 +2,6 @@
"compilerOptions": { "compilerOptions": {
"target": "ES6", "target": "ES6",
"module": "CommonJS", "module": "CommonJS",
"outDir": "./dist", "outDir": "./dist"
} }
} }
+163 -124
View File
@@ -1,153 +1,192 @@
import { executeCommand } from "../src/helper"; import { executeCommand } from "../src/helper";
import { paths } from '../src/entities'; import { paths } from "../src/entities";
describe("Set of --combined-json tests", () => { describe("Set of --combined-json tests", () => {
const zksolcCommand = 'zksolc'; const zksolcCommand = "zksolc";
const solcCommand = 'solc'; const solcCommand = "solc";
const json_args: string[] = [`abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`]; const json_args: string[] = [
`abi`,
`hashes`,
`metadata`,
`devdoc`,
`userdoc`,
`storage-layout`,
`ast`,
`asm`,
`bin`,
`bin-runtime`,
];
//id1742:I //id1742:I
describe(`Run ${zksolcCommand} with just --combined-json`, () => { describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const args = [`--combined-json`]; const args = [`--combined-json`];
const result = executeCommand(zksolcCommand, args); const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => { it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
}); });
//id1742:II it("--combined-json error is presented", () => {
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => { expect(result.output).toMatch(
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`]; /(requires a value but none was supplied)/i,
const result = executeCommand(zksolcCommand, args); );
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
}); });
//id1742:III it("solc exit code == zksolc exit code", () => {
for (let i = 0; i < json_args.length; i++) { const solcResult = executeCommand(solcCommand, args);
describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => { expect(solcResult.exitCode).toBe(result.exitCode);
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`]; });
const result = executeCommand(zksolcCommand, args); });
it("Valid command exit code = 0", () => { //id1742:II
expect(result.exitCode).toBe(0); describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
}); const args = [`${paths.pathToBasicSolContract}`, `--combined-json`];
const result = executeCommand(zksolcCommand, args);
it("--combined-json error is presented", () => { it("Valid command exit code = 1", () => {
expect(result.output).toMatch(/(contracts)/i); expect(result.exitCode).toBe(1);
}); });
it("solc exit code == zksolc exit code", () => { it("--combined-json error is presented", () => {
const solcResult = executeCommand(solcCommand, args); expect(result.output).toMatch(
expect(solcResult.exitCode).toBe(result.exitCode); /(requires a value but none was supplied)/i,
}); );
}); });
}
//id1829:I it("solc exit code == zksolc exit code", () => {
for (let i = 0; i < json_args.length; i++) { const solcResult = executeCommand(solcCommand, args);
describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => { expect(solcResult.exitCode).toBe(result.exitCode);
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `--${json_args[i]}`]; });
const result = executeCommand(zksolcCommand, args); });
it("Valid command exit code = 1", () => { //id1742:III
expect(result.exitCode).toBe(1); for (let i = 0; i < json_args.length; i++) {
}); describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
it("--combined-json error is presented", () => { it("Valid command exit code = 0", () => {
expect(result.output).toMatch(/(Invalid option|error)/i); expect(result.exitCode).toBe(0);
}); });
it("solc exit code == zksolc exit code", () => { it("--combined-json error is presented", () => {
const solcResult = executeCommand(solcCommand, args); expect(result.output).toMatch(/(contracts)/i);
expect(solcResult.exitCode).toBe(result.exitCode); });
});
});
}
//id1829:II it("solc exit code == zksolc exit code", () => {
for (let i = 0; i < json_args.length; i++) { const solcResult = executeCommand(solcCommand, args);
describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => { expect(solcResult.exitCode).toBe(result.exitCode);
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `${json_args[i]}`]; });
const result = executeCommand(zksolcCommand, args); });
}
xit("Valid command exit code = 1", () => { //id1829:I
expect(result.exitCode).toBe(1); for (let i = 0; i < json_args.length; i++) {
}); describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`--${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
it("--combined-json error is presented", () => { it("Valid command exit code = 1", () => {
expect(result.output).toMatch(/(No such file or directory|cannot find the file specified)/i); // Hopefully we should have more precise message here! expect(result.exitCode).toBe(1);
}); });
xit("solc exit code == zksolc exit code", () => { it("--combined-json error is presented", () => {
const solcResult = executeCommand(solcCommand, args); expect(result.output).toMatch(/(Invalid option|error)/i);
expect(solcResult.exitCode).toBe(result.exitCode); });
});
});
}
//id1829:III it("solc exit code == zksolc exit code", () => {
for (let i = 0; i < json_args.length; i++) { const solcResult = executeCommand(solcCommand, args);
describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => { expect(solcResult.exitCode).toBe(result.exitCode);
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `--combined-json`, `${json_args[i]}`]; });
const result = executeCommand(zksolcCommand, args); });
}
it("Valid command exit code = 1", () => { //id1829:II
expect(result.exitCode).toBe(1); for (let i = 0; i < json_args.length; i++) {
}); describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
it("--combined-json error is presented", () => { xit("Valid command exit code = 1", () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i); expect(result.exitCode).toBe(1);
}); });
it("solc exit code == zksolc exit code", () => { it("--combined-json error is presented", () => {
const solcResult = executeCommand(solcCommand, args); expect(result.output).toMatch(
expect(solcResult.exitCode).toBe(result.exitCode); /(No such file or directory|cannot find the file specified)/i,
}); ); // Hopefully we should have more precise message here!
}); });
}
//id1830 xit("solc exit code == zksolc exit code", () => {
for (let i = 0; i < json_args.length; i++) { const solcResult = executeCommand(solcCommand, args);
describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => { expect(solcResult.exitCode).toBe(result.exitCode);
const args = [`${paths.pathToBasicYulContract}`, `--combined-json`, `${json_args[i]}`]; });
const result = executeCommand(zksolcCommand, args); });
}
it("Valid command exit code = 1", () => { //id1829:III
expect(result.exitCode).toBe(1); for (let i = 0; i < json_args.length; i++) {
}); describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => {
const args = [
`${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
`--combined-json`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
it("--combined-json error is presented", () => { it("Valid command exit code = 1", () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i); expect(result.exitCode).toBe(1);
}); });
asd
it("solc exit code == zksolc exit code", () => { it("--combined-json error is presented", () => {
const solcResult = executeCommand(solcCommand, args); expect(result.output).toMatch(/(cannot be used multiple times)/i);
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
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => {
const args = [
`${paths.pathToBasicYulContract}`,
`--combined-json`,
`${json_args[i]}`,
];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i);
});
asd;
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
}); });
@@ -139,13 +139,14 @@ where
identifier.inner, identifier.inner,
) )
})?; })?;
context.build_store(pointer, value.to_llvm())?; context.build_store(pointer, value.access(context)?)?;
return Ok(()); return Ok(());
} }
let llvm_type = value.to_llvm().into_struct_value().get_type(); let value = value.access(context)?;
let llvm_type = value.into_struct_value().get_type();
let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer"); let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer");
context.build_store(tuple_pointer, value.to_llvm())?; context.build_store(tuple_pointer, value)?;
for (index, binding) in self.bindings.into_iter().enumerate() { for (index, binding) in self.bindings.into_iter().enumerate() {
context.set_debug_location(self.location.line, 0, None)?; context.set_debug_location(self.location.line, 0, None)?;
@@ -128,7 +128,10 @@ impl FunctionCall {
Name::UserDefined(name) => { Name::UserDefined(name) => {
let mut values = Vec::with_capacity(self.arguments.len()); let mut values = Vec::with_capacity(self.arguments.len());
for argument in self.arguments.into_iter().rev() { for argument in self.arguments.into_iter().rev() {
let value = argument.into_llvm(context)?.expect("Always exists").value; let value = argument
.into_llvm(context)?
.expect("Always exists")
.access(context)?;
values.push(value); values.push(value);
} }
values.reverse(); values.reverse();
@@ -461,36 +464,29 @@ impl FunctionCall {
} }
Name::SLoad => { Name::SLoad => {
let arguments = self.pop_arguments_llvm::<D, 1>(context)?; let arguments = self.pop_arguments::<D, 1>(context)?;
revive_llvm_context::polkavm_evm_storage::load( revive_llvm_context::polkavm_evm_storage::load(context, &arguments[0]).map(Some)
context,
arguments[0].into_int_value(),
)
.map(Some)
} }
Name::SStore => { Name::SStore => {
let arguments = self.pop_arguments_llvm::<D, 2>(context)?; let arguments = self.pop_arguments::<D, 2>(context)?;
revive_llvm_context::polkavm_evm_storage::store( revive_llvm_context::polkavm_evm_storage::store(
context, context,
arguments[0].into_int_value(), &arguments[0],
arguments[1].into_int_value(), &arguments[1],
) )
.map(|_| None) .map(|_| None)
} }
Name::TLoad => { Name::TLoad => {
let arguments = self.pop_arguments_llvm::<D, 1>(context)?; let arguments = self.pop_arguments::<D, 1>(context)?;
revive_llvm_context::polkavm_evm_storage::transient_load( revive_llvm_context::polkavm_evm_storage::transient_load(context, &arguments[0])
context, .map(Some)
arguments[0].into_int_value(),
)
.map(Some)
} }
Name::TStore => { Name::TStore => {
let arguments = self.pop_arguments_llvm::<D, 2>(context)?; let arguments = self.pop_arguments::<D, 2>(context)?;
revive_llvm_context::polkavm_evm_storage::transient_store( revive_llvm_context::polkavm_evm_storage::transient_store(
context, context,
arguments[0].into_int_value(), &arguments[0],
arguments[1].into_int_value(), &arguments[1],
) )
.map(|_| None) .map(|_| None)
} }
@@ -514,7 +510,7 @@ impl FunctionCall {
let offset = context.solidity_mut().allocate_immutable(key.as_str()) let offset = context.solidity_mut().allocate_immutable(key.as_str())
/ revive_common::BYTE_LENGTH_WORD; / revive_common::BYTE_LENGTH_WORD;
let index = context.xlen_type().const_int(offset as u64, false); let index = context.xlen_type().const_int(offset as u64, false);
let value = arguments[2].value.into_int_value(); let value = arguments[2].access(context)?.into_int_value();
revive_llvm_context::polkavm_evm_immutable::store(context, index, value) revive_llvm_context::polkavm_evm_immutable::store(context, index, value)
.map(|_| None) .map(|_| None)
} }
@@ -720,13 +716,13 @@ impl FunctionCall {
Name::Call => { Name::Call => {
let arguments = self.pop_arguments::<D, 7>(context)?; let arguments = self.pop_arguments::<D, 7>(context)?;
let gas = arguments[0].value.into_int_value(); let gas = arguments[0].access(context)?.into_int_value();
let address = arguments[1].value.into_int_value(); let address = arguments[1].access(context)?.into_int_value();
let value = arguments[2].value.into_int_value(); let value = arguments[2].access(context)?.into_int_value();
let input_offset = arguments[3].value.into_int_value(); let input_offset = arguments[3].access(context)?.into_int_value();
let input_size = arguments[4].value.into_int_value(); let input_size = arguments[4].access(context)?.into_int_value();
let output_offset = arguments[5].value.into_int_value(); let output_offset = arguments[5].access(context)?.into_int_value();
let output_size = arguments[6].value.into_int_value(); let output_size = arguments[6].access(context)?.into_int_value();
let simulation_address: Vec<Option<num::BigUint>> = arguments let simulation_address: Vec<Option<num::BigUint>> = arguments
.into_iter() .into_iter()
@@ -750,12 +746,12 @@ impl FunctionCall {
Name::StaticCall => { Name::StaticCall => {
let arguments = self.pop_arguments::<D, 6>(context)?; let arguments = self.pop_arguments::<D, 6>(context)?;
let gas = arguments[0].value.into_int_value(); let gas = arguments[0].access(context)?.into_int_value();
let address = arguments[1].value.into_int_value(); let address = arguments[1].access(context)?.into_int_value();
let input_offset = arguments[2].value.into_int_value(); let input_offset = arguments[2].access(context)?.into_int_value();
let input_size = arguments[3].value.into_int_value(); let input_size = arguments[3].access(context)?.into_int_value();
let output_offset = arguments[4].value.into_int_value(); let output_offset = arguments[4].access(context)?.into_int_value();
let output_size = arguments[5].value.into_int_value(); let output_size = arguments[5].access(context)?.into_int_value();
let simulation_address: Vec<Option<num::BigUint>> = arguments let simulation_address: Vec<Option<num::BigUint>> = arguments
.into_iter() .into_iter()
@@ -779,12 +775,12 @@ impl FunctionCall {
Name::DelegateCall => { Name::DelegateCall => {
let arguments = self.pop_arguments::<D, 6>(context)?; let arguments = self.pop_arguments::<D, 6>(context)?;
let gas = arguments[0].value.into_int_value(); let gas = arguments[0].access(context)?.into_int_value();
let address = arguments[1].value.into_int_value(); let address = arguments[1].access(context)?.into_int_value();
let input_offset = arguments[2].value.into_int_value(); let input_offset = arguments[2].access(context)?.into_int_value();
let input_size = arguments[3].value.into_int_value(); let input_size = arguments[3].access(context)?.into_int_value();
let output_offset = arguments[4].value.into_int_value(); let output_offset = arguments[4].access(context)?.into_int_value();
let output_size = arguments[5].value.into_int_value(); let output_size = arguments[5].access(context)?.into_int_value();
let simulation_address: Vec<Option<num::BigUint>> = arguments let simulation_address: Vec<Option<num::BigUint>> = arguments
.into_iter() .into_iter()
@@ -845,7 +841,8 @@ impl FunctionCall {
})?; })?;
revive_llvm_context::polkavm_evm_create::contract_hash(context, identifier) revive_llvm_context::polkavm_evm_create::contract_hash(context, identifier)
.map(|argument| Some(argument.value)) .and_then(|argument| argument.access(context))
.map(Some)
} }
Name::DataSize => { Name::DataSize => {
let mut arguments = self.pop_arguments::<D, 1>(context)?; let mut arguments = self.pop_arguments::<D, 1>(context)?;
@@ -855,7 +852,8 @@ impl FunctionCall {
})?; })?;
revive_llvm_context::polkavm_evm_create::header_size(context, identifier) revive_llvm_context::polkavm_evm_create::header_size(context, identifier)
.map(|argument| Some(argument.value)) .and_then(|argument| argument.access(context))
.map(Some)
} }
Name::DataCopy => { Name::DataCopy => {
let arguments = self.pop_arguments_llvm::<D, 3>(context)?; let arguments = self.pop_arguments_llvm::<D, 3>(context)?;
@@ -989,7 +987,12 @@ impl FunctionCall {
{ {
let mut arguments = Vec::with_capacity(N); let mut arguments = Vec::with_capacity(N);
for expression in self.arguments.drain(0..N).rev() { for expression in self.arguments.drain(0..N).rev() {
arguments.push(expression.into_llvm(context)?.expect("Always exists").value); arguments.push(
expression
.into_llvm(context)?
.expect("Always exists")
.access(context)?,
);
} }
arguments.reverse(); arguments.reverse();
@@ -97,9 +97,7 @@ impl Literal {
BooleanLiteral::True => num::BigUint::one(), BooleanLiteral::True => num::BigUint::one(),
}; };
Ok(revive_llvm_context::PolkaVMArgument::new_with_constant( Ok(revive_llvm_context::PolkaVMArgument::value(value).with_constant(constant))
value, constant,
))
} }
LexicalLiteral::Integer(inner) => { LexicalLiteral::Integer(inner) => {
let r#type = self.yul_type.unwrap_or_default().into_llvm(context); let r#type = self.yul_type.unwrap_or_default().into_llvm(context);
@@ -127,9 +125,7 @@ impl Literal {
} }
.expect("Always valid"); .expect("Always valid");
Ok(revive_llvm_context::PolkaVMArgument::new_with_constant( Ok(revive_llvm_context::PolkaVMArgument::value(value).with_constant(constant))
value, constant,
))
} }
LexicalLiteral::String(inner) => { LexicalLiteral::String(inner) => {
let string = inner.inner; let string = inner.inner;
@@ -200,10 +196,10 @@ impl Literal {
}; };
if hex_string.len() > revive_common::BYTE_LENGTH_WORD * 2 { if hex_string.len() > revive_common::BYTE_LENGTH_WORD * 2 {
return Ok(revive_llvm_context::PolkaVMArgument::new_with_original( return Ok(revive_llvm_context::PolkaVMArgument::value(
r#type.const_zero().as_basic_value_enum(), r#type.const_zero().as_basic_value_enum(),
string, )
)); .with_original(string));
} }
if hex_string.len() < revive_common::BYTE_LENGTH_WORD * 2 { if hex_string.len() < revive_common::BYTE_LENGTH_WORD * 2 {
@@ -220,9 +216,7 @@ impl Literal {
) )
.expect("The value is valid") .expect("The value is valid")
.as_basic_value_enum(); .as_basic_value_enum();
Ok(revive_llvm_context::PolkaVMArgument::new_with_original( Ok(revive_llvm_context::PolkaVMArgument::value(value).with_original(string))
value, string,
))
} }
} }
} }
@@ -119,36 +119,28 @@ impl Expression {
}) })
.map(Some), .map(Some),
Self::Identifier(identifier) => { Self::Identifier(identifier) => {
let id = identifier.inner;
let pointer = context let pointer = context
.current_function() .current_function()
.borrow() .borrow()
.get_stack_pointer(identifier.inner.as_str()) .get_stack_pointer(&id)
.ok_or_else(|| { .ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!("{} Undeclared variable `{}`", identifier.location, id)
"{} Undeclared variable `{}`",
identifier.location,
identifier.inner,
)
})?; })?;
let constant = context let constant = context.current_function().borrow().yul().get_constant(&id);
.current_function()
.borrow()
.yul()
.get_constant(identifier.inner.as_str());
let value = context.build_load(pointer, identifier.inner.as_str())?; let argument = revive_llvm_context::PolkaVMArgument::pointer(pointer, id);
match constant { Ok(Some(match constant {
Some(constant) => Ok(Some( Some(constant) => argument.with_constant(constant),
revive_llvm_context::PolkaVMArgument::new_with_constant(value, constant), _ => argument,
)), }))
None => Ok(Some(value.into())),
}
} }
Self::FunctionCall(call) => Ok(call Self::FunctionCall(call) => Ok(call
.into_llvm(context)? .into_llvm(context)?
.map(revive_llvm_context::PolkaVMArgument::new)), .map(revive_llvm_context::PolkaVMArgument::value)),
} }
} }
} }
@@ -78,7 +78,7 @@ where
.condition .condition
.into_llvm(context)? .into_llvm(context)?
.expect("Always exists") .expect("Always exists")
.to_llvm() .access(context)?
.into_int_value(); .into_int_value();
let condition = context.builder().build_int_z_extend_or_bit_cast( let condition = context.builder().build_int_z_extend_or_bit_cast(
condition, condition,
@@ -57,7 +57,7 @@ where
.condition .condition
.into_llvm(context)? .into_llvm(context)?
.expect("Always exists") .expect("Always exists")
.to_llvm() .access(context)?
.into_int_value(); .into_int_value();
let condition = context.builder().build_int_z_extend_or_bit_cast( let condition = context.builder().build_int_z_extend_or_bit_cast(
condition, condition,
@@ -209,6 +209,8 @@ where
revive_llvm_context::PolkaVMRemainderFunction.declare(context)?; revive_llvm_context::PolkaVMRemainderFunction.declare(context)?;
revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?; revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?;
revive_llvm_context::PolkaVMSbrkFunction.declare(context)?;
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default(); let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
entry.declare(context)?; entry.declare(context)?;
@@ -261,6 +263,8 @@ where
revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?; revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?; revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?;
Ok(()) Ok(())
} }
@@ -137,7 +137,7 @@ where
let mut branches = Vec::with_capacity(self.cases.len()); let mut branches = Vec::with_capacity(self.cases.len());
for (index, case) in self.cases.into_iter().enumerate() { for (index, case) in self.cases.into_iter().enumerate() {
let constant = case.literal.into_llvm(context)?.to_llvm(); let constant = case.literal.into_llvm(context)?.access(context)?;
let expression_block = context let expression_block = context
.append_basic_block(format!("switch_case_branch_{}_block", index + 1).as_str()); .append_basic_block(format!("switch_case_branch_{}_block", index + 1).as_str());
@@ -161,7 +161,10 @@ where
context.set_basic_block(current_block); context.set_basic_block(current_block);
context.builder().build_switch( context.builder().build_switch(
scrutinee.expect("Always exists").to_llvm().into_int_value(), scrutinee
.expect("Always exists")
.access(context)?
.into_int_value(),
default_block, default_block,
branches.as_slice(), branches.as_slice(),
)?; )?;
@@ -121,7 +121,7 @@ where
.insert_constant(identifier.inner.clone(), constant); .insert_constant(identifier.inner.clone(), constant);
} }
value.to_llvm() value.access(context)?
} }
None => r#type.const_zero().as_basic_value_enum(), None => r#type.const_zero().as_basic_value_enum(),
} }
@@ -175,7 +175,8 @@ where
.collect::<Vec<inkwell::types::BasicTypeEnum<'ctx>>>() .collect::<Vec<inkwell::types::BasicTypeEnum<'ctx>>>()
.as_slice(), .as_slice(),
); );
if expression.value.get_type() != llvm_type.as_basic_type_enum() { let value = expression.access(context)?;
if value.get_type() != llvm_type.as_basic_type_enum() {
anyhow::bail!( anyhow::bail!(
"{} Assignment to {:?} received an invalid number of arguments", "{} Assignment to {:?} received an invalid number of arguments",
location, location,
@@ -183,7 +184,7 @@ where
); );
} }
let pointer = context.build_alloca(llvm_type, "bindings_pointer"); let pointer = context.build_alloca(llvm_type, "bindings_pointer");
context.build_store(pointer, expression.to_llvm())?; context.build_store(pointer, value)?;
for (index, binding) in self.bindings.into_iter().enumerate() { for (index, binding) in self.bindings.into_iter().enumerate() {
let pointer = context.build_gep( let pointer = context.build_gep(
-1
View File
@@ -1 +0,0 @@
../../../target/wasm32-unknown-emscripten/release/resolc.js
-1
View File
@@ -1 +0,0 @@
../../../target/wasm32-unknown-emscripten/release/resolc.wasm
-20
View File
@@ -1,20 +0,0 @@
{
"language": "Solidity",
"sources": {
"fixtures/instantiate.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.8.2 <0.9.0;\ncontract ChildContract {\n constructor() {\n }\n}\ncontract MainContract {\n constructor() {\n ChildContract newContract = new ChildContract();\n }\n}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": ["abi"]
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
+20
View File
@@ -0,0 +1,20 @@
# @parity/revive
Node.js module to compile Solidity contracts to Polkavm bytecode, using [Revive](https://github.com/paritytech/revive)
# Usage from Node.js
```typescript
const sources = {
["contracts/1_Storage.sol"]: {
content: readFileSync("fixtures/storage.sol", "utf8"),
}
const out = await compile(sources);
```
# Usage from shell
```bash
npx @parity/revive@latest --bin contracts/1_Storage.sol
```
+11
View File
@@ -0,0 +1,11 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
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,
]
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^9999;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract BadPragma {
}
+28
View File
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
+26
View File
@@ -0,0 +1,26 @@
// missing license and pragama
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{
_mint(msg.sender, 100 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "@parity/resolc",
"license": "Apache-2.0",
"version": "0.0.0-updated-via-gh-releases",
"author": "Parity <admin@parity.io> (https://parity.io)",
"module": "index.ts",
"types": "./dist/index.d.ts",
"main": "./dist/index.js",
"bin": {
"resolc": "./dist/bin.js"
},
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && cp src/resolc/** dist/resolc",
"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"
},
"dependencies": {
"commander": "^13.1.0",
"package-json": "^10.0.1",
"solc": "^0.8.29"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
+203
View File
@@ -0,0 +1,203 @@
#!/usr/bin/env node
import * as commander from 'commander'
import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as resolc from '.'
import { SolcInput } from '.'
async function main() {
// hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
const originalUncaughtExceptionListeners =
process.listeners('uncaughtException')
// FIXME: remove annoying exception catcher of Emscripten
// see https://github.com/chriseth/browser-solidity/issues/167
process.removeAllListeners('uncaughtException')
const program = new commander.Command()
program.name('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 ?? '.'
function abort(msg: string) {
console.error(msg || 'Error occurred')
process.exit(1)
}
function withUnixPathSeparators(filePath: string) {
// On UNIX-like systems forward slashes in paths are just a part of the file name.
if (os.platform() !== 'win32') {
return filePath
}
return filePath.replace(/\\/g, '/')
}
function makeSourcePathRelativeIfPossible(sourcePath: string) {
const absoluteBasePath = options.basePath
? path.resolve(options.basePath)
: path.resolve('.')
const absoluteIncludePaths = options.includePath
? options.includePath.map((prefix: string) => {
return path.resolve(prefix)
})
: []
// Compared to base path stripping logic in solc this is much simpler because path.resolve()
// handles symlinks correctly (does not resolve them except in work dir) and strips .. segments
// from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it
// ignores less important corner cases: drive letters are not stripped from absolute paths on
// Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has
// very little test coverage so there might be more differences that we are just not aware of.
const absoluteSourcePath = path.resolve(sourcePath)
for (const absolutePrefix of [absoluteBasePath].concat(
absoluteIncludePaths
)) {
const relativeSourcePath = path.relative(
absolutePrefix,
absoluteSourcePath
)
if (!relativeSourcePath.startsWith('../')) {
return withUnixPathSeparators(relativeSourcePath)
}
}
// File is not located inside base path or include paths so use its absolute path.
return withUnixPathSeparators(absoluteSourcePath)
}
function toFormattedJson<T>(input: T) {
return JSON.stringify(input, null, options.prettyJson ? 4 : 0)
}
if (files.length === 0) {
console.error('Must provide a file')
process.exit(1)
}
if (!(options.bin || options.abi)) {
abort('Invalid option selected, must specify either --bin or --abi')
}
const sources: SolcInput = {}
for (let i = 0; i < files.length; i++) {
try {
sources[makeSourcePathRelativeIfPossible(files[i])] = {
content: fs.readFileSync(files[i]).toString(),
}
} catch (e) {
abort('Error reading ' + files[i] + ': ' + e)
}
}
if (options.verbose) {
console.log('>>> Compiling:\n' + toFormattedJson(sources) + '\n')
}
const output = await resolc.compile(sources)
let hasError = false
if (!output) {
abort('No output from compiler')
} else if (output.errors) {
for (const error in output.errors) {
const message = output.errors[error]
if (message.severity === 'warning') {
console.log(message.formattedMessage)
} else {
console.error(message.formattedMessage)
hasError = true
}
}
}
fs.mkdirSync(destination, { recursive: true })
function writeFile(file: string, content: Buffer | string) {
file = path.join(destination, file)
fs.writeFile(file, content, function (err) {
if (err) {
console.error('Failed to write ' + file + ': ' + err)
}
})
}
for (const fileName in output.contracts) {
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)
})
+132
View File
@@ -0,0 +1,132 @@
import { test } from 'node:test'
import { readFileSync } 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' })
}
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'),
},
}
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 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'
)
)
})
test('check Err from stderr', async () => {
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'
)
)
}
})
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`,
},
]
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}`
)
}
}
})
+202
View File
@@ -0,0 +1,202 @@
import solc from 'solc'
import { spawn } from 'child_process'
import { resolc, version as resolcVersion } from './resolc'
import path from 'path'
import { existsSync, readFileSync } from 'fs'
export type SolcInput = {
[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
}
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 }
}
}
}
}
errors?: Array<SolcError>
}
export function resolveInputs(sources: SolcInput): SolcInput {
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 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
}
export async function compile(
sources: SolcInput,
option: { bin?: string } = {}
): Promise<SolcOutput> {
const input = JSON.stringify({
language: 'Solidity',
sources: resolveInputs(sources),
settings: {
optimizer: { enabled: true, runs: 200 },
outputSelection: {
'*': {
'*': ['abi'],
},
},
},
})
if (option.bin) {
return compileWithBin(input, option.bin)
}
return resolc(input)
}
/**
* Resolve an import path to a file path.
* @param importPath - The import path to resolve.
*/
export function tryResolveImport(importPath: string) {
// 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 packageRoot = path.dirname(packageJsonPath)
// Construct full path to the requested file
const resolvedPath = path.join(packageRoot, relativePath)
if (existsSync(resolvedPath)) {
return resolvedPath
} else {
throw new Error(`Resolved path ${resolvedPath} does not exist.`)
}
}
function compileWithBin(input: string, bin: string): PromiseLike<SolcOutput> {
return new Promise((resolve, reject) => {
const process = spawn(bin, ['--standard-json'])
let output = ''
let error = ''
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}`))
}
})
})
}
+30
View File
@@ -0,0 +1,30 @@
import soljson from 'solc/soljson'
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()
if (err) {
throw new Error(err)
}
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()
if (err) {
throw new Error(err)
}
return m.readFromStdout()
}
View File
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.js
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.wasm
+95
View File
@@ -0,0 +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
}
}
settings?: {
optimizer?: {
enabled: boolean
runs: number
}
outputSelection: {
[fileName: string]: {
[contractName: string]: string[]
}
}
}
}
export interface CompileOutput {
errors?: Array<{
component: string
errorCode: string
formattedMessage: string
message: string
severity: string
sourceLocation?: {
file: string
start: number
end: number
}
type: string
}>
sources?: {
[fileName: string]: {
id: number
ast: object
}
}
contracts?: {
[fileName: string]: {
[contractName: string]: {
abi: object[]
evm: {
bytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
deployedBytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
}
}
}
}
}
// Main exported functions
export function compile(
input: string | CompileInput,
options?: {
import: (path: string) =>
| {
contents: string
error?: undefined
}
| {
error: string
contents?: undefined
}
}
): string
}
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"sourceMap": true,
"declaration": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"moduleResolution": "node",
"module": "commonjs",
"target": "ES2022",
"lib": ["ES2022"],
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"useUnknownInCatchVariables": true,
"types": ["node"],
"typeRoots": ["./node_modules/@types", "./src"],
"paths": {
"#src/*": ["./src/*"]
},
"plugins": []
},
"include": ["src/**/*"]
}
+1 -1
View File
@@ -8,7 +8,7 @@ const RESOLC_WASM_URI =
process.env.RELEASE_RESOLC_WASM_URI || "http://127.0.0.1:8080/resolc.wasm"; process.env.RELEASE_RESOLC_WASM_URI || "http://127.0.0.1:8080/resolc.wasm";
const RESOLC_WASM_TARGET_DIR = path.join( const RESOLC_WASM_TARGET_DIR = path.join(
__dirname, __dirname,
"../target/wasm32-unknown-emscripten/release", "../../target/wasm32-unknown-emscripten/release",
); );
const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js"); const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js");
const RESOLC_WEB_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc_web.js"); const RESOLC_WEB_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc_web.js");
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.js
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.wasm
+19
View File
@@ -0,0 +1,19 @@
{
"language": "Solidity",
"sources": {
"fixtures/instantiate.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.8.2 <0.9.0;\ncontract ChildContract {\n constructor() {\n }\n}\ncontract MainContract {\n constructor() {\n ChildContract newContract = new ChildContract();\n }\n}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": ["abi"]
}
}
}
}
@@ -72,9 +72,7 @@
}, },
"outputSelection": { "outputSelection": {
"*": { "*": {
"*": [ "*": ["abi"]
"abi"
]
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More